字符串源码
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()
,都不是在原有的字符串上进行的,而是重新生成了新的字符串对象。也就是说,这些操作执行过后,原来的字符串对象并没有发生改变