九、解读String源码
String类的声明(jdk8版本)
            
            
              java
              
              
            
          
          public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];
    private int hash;
}
第一,String类是final的,意味着它不能被子类继承。
第二, String类实现了Serializable接口,意味着它可以序列化。
第三,String类实现了Comparable接口,意味着最好不要用 == 来比较两个字符串是否相等,而应该用 compareTO()方法去比较。因为 == 是用来比较两个对象的地址。如果只是比较两个字符串的内容的话,推荐使用String类中的 equals 方法
第四,String和StringBuffer、StringBuilder 一样,都实现了CharSequence接口,所以它们三个属于近亲。由于String是不可变的,所以遇到字符串拼接的时候就可以考虑一下String的另外连个好兄弟,StringBuffer 和 StringBuilder, 它俩是可变的。
equals方法的源码:
            
            
              java
              
              
            
          
          public boolean equals(Object anObject) {
    // 检查是否是同一个对象的引用,如果是,直接返回 true
    if (this == anObject) {
        return true;
    }
    // 检查 anObject 是否是 String 类的实例
    if (anObject instanceof String) {
        String anotherString = (String) anObject; // 将 anObject 强制转换为 String 类型
        int n = value.length; // 获取当前字符串的长度
        // 检查两个字符串长度是否相等
        if (n == anotherString.value.length) {
            char v1[] = value; // 当前字符串的字符数组
            char v2[] = anotherString.value; // 另一个字符串的字符数组
            int i = 0; // 用于遍历字符数组的索引
            // 遍历比较两个字符串的每个字符
            while (n-- != 0) {
                // 如果在任何位置字符不同,则返回 false
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            // 所有字符都相同,返回 true
            return true;
        }
    }
    // 如果 anObject 不是 String 类型或长度不等,则返回 false
    return false;
}1、String底层为什么由char数组优化为byte数组
- 第五,Java 9 以前,String 是用 char 型数组实现的,之后改成了 byte 型数组实现,并增加了 coder 来表示编码。这样做的好处是在 Latin1 字符为主的程序里,可以把 String 占用的内存减少一半。当然,天下没有免费的午餐,这个改进在节省内存的同时引入了编码检测的开销。
Latin1(Latin-1)是一种单字节字符集(即每个字符只使用一个字节的编码方式),也称为 ISO-8859-1(国际标准化组织 8859-1),它包含了西欧语言中使用的所有字符,包括英语、法语、德语、西班牙语、葡萄牙语、意大利语等等。在 Latin1 编码中,每个字符使用一个 8 位(即一个字节)的编码,可以表示 256 种不同的字符,其中包括 ASCII 字符集中的所有字符,即 0x00 到 0x7F,以及其他西欧语言中的特殊字符,例如 é、ü、ñ 等等。由于 Latin1 只使用一个字节表示一个字符,因此在存储和传输文本时具有较小的存储空间和较快的速度
下面是jdk11版本中的String类源码,注意和jdk8有所不同
            
            
              java
              
              
            
          
          public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    @Stable
    private final byte[] value;
    private final byte coder;
    private int hash;
}从
char[]到byte[],最主要的目的是节省字符串占用的内存空间。内存占用减少带来的另外一个好处,就是GC次数也会减少GC,也就是垃圾回收,JVM的时候,会讲。
2、String类的hashCode方法
- "第六,每一个字符串都会有一个 hash 值,这个哈希值在很大概率是不会重复的,因此 String 很适合作为HashMap的键值。
看一下String类中的hashCode方法
            
            
              java
              
              
            
          
          private int hash; // 缓存字符串的哈希码
public int hashCode() {
    int h = hash; // 从缓存中获取哈希码
    // 如果哈希码未被计算过(即为 0)且字符串不为空,则计算哈希码
    if (h == 0 && value.length > 0) {
        char val[] = value; // 获取字符串的字符数组
        // 遍历字符串的每个字符来计算哈希码
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i]; // 使用 31 作为乘法因子
        }
        hash = h; // 缓存计算后的哈希码
    }
    return h; // 返回哈希码
}hashCode 方法首先检查是否已经计算过哈希码,如果已经计算过,则直接返回缓存的哈希码。否则,方法将使用一个循环遍历字符串的所有字符,并使用一个乘法和加法的组合计算哈希码。
这种计算方法被称为"31 倍哈希法"。计算完成后,将得到的哈希值存储在 hash 成员变量中,以便下次调用 hashCode 方法时直接返回该值,而不需要重新计算。这是一种缓存优化,称为"惰性计算"。
31 倍哈希法(31-Hash)是一种简单有效的字符串哈希算法,常用于对字符串进行哈希处理。该算法的基本思想是将字符串中的每个字符乘以一个固定的质数 31 的幂次方,并将它们相加得到哈希值。具体地,假设字符串为 s,长度为 n,则 31 倍哈希值计算公式如下:
            
            
              java
              
              
            
          
          H(s) = (s[0] * 31^(n-1)) + (s[1] * 31^(n-2)) + ... + (s[n-1] * 31^0)其中,s[i]表示字符串 s 中第 i 个字符的 ASCII 码值,
^表示幂运算。31 倍哈希法的优点在于简单易实现,计算速度快,同时也比较均匀地分布在哈希表中。
我们可以通过一下方法进行模拟String的hashCode方法
            
            
              java
              
              
            
          
          public class HashCodeExample {
    public static void main(String[] args) {
        String text = "浩妹";
        int hashCode = computeHashCode(text);
        System.out.println("字符串 \"" + text + "\" 的哈希码是: " + hashCode);
        System.out.println("String 的 hashCode " + text.hashCode());
    }
    public static int computeHashCode(String text) {
        int h = 0;
        for (int i = 0; i < text.length(); i++) {
            h = 31 * h + text.charAt(i);
        }
        return h;
    }
}结果
            
            
              java
              
              
            
          
          字符串 "浩妹" 的哈希码是: 867758096
String 的 hashCode 8677580963、String类中的substring方法
String 类中还有一个方法比较常用 substring,用来截取字符串的,来看源码。
            
            
              java
              
              
            
          
          public String substring(int beginIndex) {
    // 检查起始索引是否小于 0,如果是,则抛出 StringIndexOutOfBoundsException 异常
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    // 计算子字符串的长度
    int subLen = value.length - beginIndex;
    // 检查子字符串长度是否为负数,如果是,则抛出 StringIndexOutOfBoundsException 异常
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    // 如果起始索引为 0,则返回原字符串;否则,创建并返回新的字符串
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}substring 方法首先检查参数的有效性,如果参数无效,则抛出 StringIndexOutOfBoundsException 异常(后面会细讲)。接下来,方法根据参数计算子字符串的长度。如果子字符串长度小于零,也会抛出 StringIndexOutOfBoundsException 异常。
如果 beginIndex 为 0,说明子串与原字符串相同,直接返回原字符串。否则,使用 value 数组(原字符串的字符数组)的一部分 new 一个新的 String 对象并返回。
下面是几个使用 substring 方法的示例:
提取字符串中的一段子串:
String str = "Hello, world!";
String subStr = str.substring(7, 12);  // 从第7个字符(包括)提取到第12个字符(不包括)
System.out.println(subStr);  // 输出 "world"提取字符串中的前缀或者后缀
            
            
              java
              
              
            
          
          String str = "Hello, world!";
String prefix = str.substring(0, 5);  // 提取前5个字符,即 "Hello"
String suffix = str.substring(7);     // 提取从第7个字符开始的所有字符,即 "world!"处理字符串中的空格和分隔符
            
            
              java
              
              
            
          
          String str = "    Hello,     world!   ";
String trimmed = str.trim();
String[] words = trimmed.split("String str = "   Hello,   world!  ";
String trimmed = str.trim();                  // 去除字符串开头和结尾的空格
String[] words = trimmed.split("\\s+");       // 将字符串按照空格分隔成单词数组
String firstWord = words[0].substring(0, 1);  // 提取第一个单词的首字母
System.out.println(firstWord);                // 输出 "H"处理字符串的数字和符号
            
            
              java
              
              
            
          
          String str = "1234-5678-9012-3456";
String[] parts = str.split("-");             // 将字符串按照连字符分隔成四个部分
String last4Digits = parts[3].substring(1);  // 提取最后一个部分的后三位数字
System.out.println(last4Digits);             // 输出 "456"总之,substring 方法可以根据需求灵活地提取字符串中的子串,为字符串处理提供了便利。
4、String类的indexOf方法
indexOf 方法用于查找一个子字符串在原字符串中第一次出现的位置,并返回该位置的索引。来看该方法的源码:
            
            
              java
              
              
            
          
          /*
 * 查找字符数组 target 在字符数组 source 中第一次出现的位置。
 * sourceOffset 和 sourceCount 参数指定 source 数组中要搜索的范围,
 * targetOffset 和 targetCount 参数指定 target 数组中要搜索的范围,
 * fromIndex 参数指定开始搜索的位置。
 * 如果找到了 target 数组,则返回它在 source 数组中的位置索引(从0开始),
 * 否则返回-1。
 */
static int indexOf(char[] source, int sourceOffset, int sourceCount,
        char[] target, int targetOffset, int targetCount,
        int fromIndex) {
    // 如果开始搜索的位置已经超出 source 数组的范围,则直接返回-1(如果 target 数组为空,则返回 sourceCount)
    if (fromIndex >= sourceCount) {
        return (targetCount == 0 ? sourceCount : -1);
    }
    // 如果开始搜索的位置小于0,则从0开始搜索
    if (fromIndex < 0) {
        fromIndex = 0;
    }
    // 如果 target 数组为空,则直接返回开始搜索的位置
    if (targetCount == 0) {
        return fromIndex;
    }
    // 查找 target 数组的第一个字符在 source 数组中的位置
    char first = target[targetOffset];
    int max = sourceOffset + (sourceCount - targetCount);
    // 循环查找 target 数组在 source 数组中的位置
    for (int i = sourceOffset + fromIndex; i <= max; i++) {
        /* Look for first character. */
        // 如果 source 数组中当前位置的字符不是 target 数组的第一个字符,则在 source 数组中继续查找 target 数组的第一个字符
        if (source[i] != first) {
            while (++i <= max && source[i] != first);
        }
        /* Found first character, now look at the rest of v2 */
        // 如果在 source 数组中找到了 target 数组的第一个字符,则继续查找 target 数组的剩余部分是否匹配
        if (i <= max) {
            int j = i + 1;
            int end = j + targetCount - 1;
            for (int k = targetOffset + 1; j < end && source[j]
                    == target[k]; j++, k++);
            // 如果 target 数组全部匹配,则返回在 source 数组中的位置索引
            if (j == end) {
                /* Found whole string. */
                return i - sourceOffset;
            }
        }
    }
    // 没有找到 target 数组,则返回-1
    return -1;
}查找子字符串的位置
            
            
              java
              
              
            
          
          String str = "Hello, world!";
int index = str.indexOf("world");  // 查找 "world" 子字符串在 str 中第一次出现的位置
System.out.println(index);        // 输出 7查找字符串中某个字符的位置
            
            
              java
              
              
            
          
          String str = "Hello, world!";
int index = str.indexOf(",");     // 查找逗号在 str 中第一次出现的位置
System.out.println(index);        // 输出 5查找子字符串的位置(从指定位置开始查找)
            
            
              java
              
              
            
          
          String str = "Hello, world!";
int index = str.indexOf("l", 3);  // 从索引为3的位置开始查找 "l" 子字符串在 str 中第一次出现的位置
System.out.println(index);        // 输出 3查找多个子字符串
            
            
              java
              
              
            
          
          String str = "Hello, world!";
int index1 = str.indexOf("o");    // 查找 "o" 子字符串在 str 中第一次出现的位置
int index2 = str.indexOf("o", 5); // 从索引为5的位置开始查找 "o" 子字符串在 str 中第一次出现的位置
System.out.println(index1);       // 输出 4
System.out.println(index2);       // 输出 85、String类的其他方法
比如说 length() 用于返回字符串的长度
比如说 isEmpty() 用于判断字符串是否为空
比如说 charAt() 用于返回指定索引处的字符
比如说 valueOf() 用于将其他类型的数据转换为字符串
            
            
              java
              
              
            
          
          String str = String.valueOf(1223); //将整数类型1223转换为字符串类型valueOf 方法的背后其实调用的是包装器的toString方法, 比如说整数转为字符串调用的就是Integer类的toString方法
            
            
              java
              
              
            
          
          public static String valueOf(int i) {
    return Integer.toString(i);
}而 Integer 类的 toString 方法又调用了 Integer 类的静态方法
toString(int i):
            
            
              java
              
              
            
          
          public static String toString(int i) {
    // 最小值返回 "-2147483648"
    if (i == Integer.MIN_VALUE)
        return "-2147483648";
    // 整数的长度,负数的长度减 1
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    // 把整数复制到字符数组中
    char[] buf = new char[size];
    // 具体的复制过程
    getChars(i, size, buf);
    // 通过 new 返回字符串
    return new String(buf, true);
}
- 比如说 getBytes() 用于返回字符串的字节数组,可以指定编码格式
            
            
              java
              
              
            
          
          String str = "浩妹";
System.out.println(Arrays.toString(text.getBytes(StandardCharsets.UTF_8)));
- 比如说 trim() 用于去除返回字符串两侧的空白字符
            
            
              java
              
              
            
          
          public String trim() {
    int len = value.length;
    int st = 0;
    char[] val = value;    /* avoid getfield opcode */
    while ((st < len) && (val[st] <= ' ')) {
        st++;
    }
    while ((st < len) && (val[len - 1] <= ' ')) {
        len--;
    }
    return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
- 比如说
toCharArray()用于将字符串转换为字符数组。
            
            
              java
              
              
            
          
          String str = "浩妹";
char[] chars = str.toCharArray();
System.out.println(Arrays.toString(chars));