String对于我们都很熟悉,平时用的也不少,但是小伙伴们对于String的源码究竟了解多少?如果你真的了解String的源码,会避免很多坑,那我们开始吧。

String类
1  | public final class String implements java.io.Serializable, Comparable<String>, CharSequence  | 
从String类的定义中,我们可以认识到
String类被final修饰,表示该类不能被别的类继承。String类实现了
java.io.Serializable接口,表示String类可以序列化。Serializable是一个空接口,它的实现类无需重写任何方法,只要实现了该接口,即相当于开启可序列化操作标识。String类实现了
Comparable<String>,字符串可以比较大小。String类通过重写Comparable接口的compareTo(T o)方法,实现字符串比较的逻辑。String类实现了
CharSequence接口,表示是一个字符的序列,因为String的本质是一个char类型数组。java.lang.CharSequence接口是一个可读的字符序列。此接口为不同类型的字符序列提供了一种统一且只读的访问方式。在接口中定义的方法包括charAt(int index)、length()、toString()等方法。
字段属性
1  | /** The value is used for character storage.*/  | 
byte[] value数组是String类底层的数据结构,即String类是存储在value数组里面。需要注意的是该数组被
final修饰,对于final修饰的数组,我们可以修改该数组的对象的值,但是不能改变指向的对象即不能指向别的数组。测试代码
1  | public class ArrayTest {  | 
 结果
1  | Java  | 
需要注意,在测试代码里面,即代码第七行,指向别的数组对象,编译直接报错。
int hash用来保存某一个String实例的哈希值,可以说是哈希值的一个缓存。例如,当
String放入HashMap中,作为key来使用。每次插入一个键值対时,不需要重新计算key的哈希值,直接取出key的缓存hash值即可,在一定程度上,加快了HashMap的效率。long serialVersionUID用于保证版本一致性。String实现了
Serializable接口,因此需要拥有一个序列化的ID。序列化时,将此ID与对象一并写入到文件中,反序列化时,检测该类中的ID与文件中的ID是否一致,一致的话,说明版本一致,序列化成功。
需要注意,当实现
Serializable接口,如果不声明long serialVersionUID的值,也可以进行序列化和反序列化。因为Java会帮助我们自动生成一个long serialVersionUID的值。但是需要注意的是,如果我们修改实现
Serializable接口的类时,Java自动生成的long serialVersionUID的值也会发生改变。如果我们需要保证程序向前兼容,此时会报错,因为该值发生了改变。即我们本地序列化的serialVersionUID的值和我们类里的serialVersionUID的值不一致,无法序列化,报错。如果我们自己声明该值,并且保证该值不变,相同情况下,可以顺利序列化,保证程序的向前兼容性。想要深入了解,可以参考文献的第一篇文章。
构造方法
- 无参构造方法
 
创建空的字符串,因为
String是不可变的,所以该构造方法使用较少。
1  | /**  | 
- 接收
String类型参数的构造函数 
1  | public String(String original) {  | 
- 接收
char[] 数组类型参数的构造函数 
1  | public String(char value[]) {  | 
1  | public static char[] copyOf(char[] original, int newLength) {  | 
根据代码块我们可以发现构造函数调用了Array的copyof方法,而copyof方法基于System.arraycopy方法。System.arraycopy方法是native方法,由JVM调用非Java方法实现。
- 通过解码指定字节数组,生成字符串。
 
1  | public String(byte bytes[], int offset, int length, String charsetName)  | 
常用方法
- 字符串本身的属性
 
1  | public int length() {  | 
String底层实现方式是value数组,即数组的长度。
1  | public boolean isEmpty() {  | 
String底层实现方式是value数组,字符串为空,即数组的长度为零。
- 字符串之间的比较
 
1  | public boolean equals(Object anObject) {  | 
String重写了Object类的equals方法,如果是同一个对象直接返回。否则逐一比较两个字符串的每个char元素。
1  | public int compareTo(String anotherString) {  | 
String实现了Comparable接口的compareTo方法,从起始位置开始,比较char元素的大小。
字符串处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}String的substring方法用于截断字符串,返回字符串的一部分。我们可以发现返回的是一个新的字符串,而不是在之前的字符串上面修改,因为之前的字符串是不可变的。1
2
3
4
5
6
7
8
9
10public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}String的concat方法用于拼接字符串,将str拼接到现有的字符串上,返回拼接好的字符串。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}String的replace方法用于将字符串的oldChar替换成newChar,返回新的字符串。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53public String[] split(String regex, int limit) {
/* fastpath if the regex is a
(1)one-char String and this character is not one of the
RegEx's meta characters ".$|()[{^?*+\\", or
(2)two-char String and the first char is the backslash and
the second is not the ascii digit or ascii letter.
*/
char ch = 0;
if (((regex.value.length == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
(regex.length() == 2 &&
regex.charAt(0) == '\\' &&
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
((ch-'a')|('z'-ch)) < 0 &&
((ch-'A')|('Z'-ch)) < 0)) &&
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE))
{
int off = 0;
int next = 0;
boolean limited = limit > 0;
ArrayList<String> list = new ArrayList<>();
while ((next = indexOf(ch, off)) != -1) {
if (!limited || list.size() < limit - 1) {
list.add(substring(off, next));
off = next + 1;
} else { // last one
//assert (list.size() == limit - 1);
list.add(substring(off, value.length));
off = value.length;
break;
}
}
// If no match was found, return this
if (off == 0)
return new String[]{this};
// Add remaining segment
if (!limited || list.size() < limit)
list.add(substring(off, value.length));
// Construct result
int resultSize = list.size();
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
}
}
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);
}
return Pattern.compile(regex).split(this, limit);
}String的split方法用于使用字符串的regex分割字符串,返回新的字符串数组。源码解析可以查看参考文献2。