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。