字符串源码
String类的声明
// final不可被继承 public final class String implements java.io.Serializable, Comparable<String>, CharSequence { }
- 比较字符串内容
public boolean equals(Object anObject) { // 检查是否是同一个对象的引用,如果是,直接返回 true if (this == anObject) { return true; } // 检查 anObject 是否是 String 类的实例 if (anObject instanceof String) { // 将 anObject 强制转换为 String 类型 String anotherString = (String)anObject; // 获取当前字符串的长度 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; }
char 数组优化为 byte 数组
-
Java 9 以前,String 是用 char 型数组实现的,之后改成了 byte 型数组实现,并增加了 coder 来表示编码。这样做的好处是在 Latin1 字符(一种单字节字符集)为主的程序里,可以把 String 占用的内存减少一半。
-
从
char[]
到byte[]
,最主要的目的是节省字符串占用的内存空间。内存占用减少带来的另外一个好处,就是 GC 次数也会减少。 -
JDK11中String类源码
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { @Stable private final byte[] value; private final byte coder; private int hash; }
31倍哈希法
public int hashCode() { // 从缓存中获取哈希码 int h = hash; // 如果哈希码未被计算过(即为 0)且字符串不为空,则计算哈希码 if (h == 0 && value.length > 0) { // 获取字符串的字符数组 char val[] = value; // 遍历字符串的每个字符来计算哈希码 for (int i = 0; i < value.length; i++) { // 31 倍哈希法: 使用 31 作为乘法因子 h = 31 * h + val[i]; } // 缓存计算后的哈希码 hash = h; } // 返回哈希码 return h; }
subString()
- 截取字符串
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); }
indexOf()
- 查找一个子字符串在原字符串中第一次出现的位置,并返回该位置的索引
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); for (int i = sourceOffset + fromIndex; i <= max; i++) { // 如果 source 数组中当前位置的字符不是 target 数组的第一个字符,则在 source 数组中继续查找 target 数组的第一个字符 /* Look for first character. */ if (source[i] != first) { while (++i <= max && source[i] != first); } // 如果在 source 数组中找到了 target 数组的第一个字符,则继续查找 target 数组的剩余部分是否匹配 /* Found first character, now look at the rest of v2 */ 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; }
trim()
- 去除字符串两侧的空白字符
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; }
字符串不可变
-
可以保证 String 对象的安全性,避免被篡改
-
保证哈希值不会频繁变更
-
可以实现字符串常量池,Java 会将相同内容的字符串存储在字符串常量池中。这样,具有相同内容的字符串变量可以指向同一个 String 对象,节省内存空间
-
不管是截取
substring()
、拼接concat()
,还是替换replace()
,都不是在原有的字符串上进行的,而是重新生成了新的字符串对象。也就是说,这些操作执行过后,原来的字符串对象并没有发生改变
本文作者:n1ce2cv
本文链接:https://www.cnblogs.com/sprinining/p/18301345
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步