String中的final
首先来看String的定义,用了final修饰,所以String类是无法被继承的
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
value这个变量就是用来存储字符串值的关键变量,之前jdk8的时候使用的是char数组,到了jdk9这里换成了byte数组
@Stable
private final byte[] value;
因为用final定义了数组,说明value指向的地址是不可改变的,但是数组的内容是可以改变的,不过因为这个byte数组是私有的,并且String类里面并没有提供修改byte数组的接口,使得String类中的value不可改变,就算是replace方法,在实现里面也是new了一个新的String
用个小例子说明下final修饰数组的情况:
final char[] s = {'2', '4', '5'};
s[0] = '1';
System.out.println(s); // 输出145
String s = "123"; // 报错
String字符串压缩
再往下看一行,发现多了一个叫做coder的东西,这东西究竟是个啥?
private final byte coder;
结合整个String类的源码研究了一下,原来自从jdk9开始,底层的存储由char数组换成了byte数组,因为在大部分情况下,String所存储的字符可以只用latin1来编码,latin1一个字符只占1个字节,char一个字符要占两个字节,这样子就可以节省空间,所以我们在接下来的源码中可以看到很多对于编码格式的处理
看到COMPACT_STRINGS这个参数,这个参数代表是否支持字符串压缩,默认设置为true
static final boolean COMPACT_STRINGS;
static {
COMPACT_STRINGS = true;
}
把String翻到最下面,这里定义了String用到的两种编码方式,一种是UTF16,设置值为1,另一种就是LATIN1,设置值为1,只有当COMPACT_STRINGS为真,且编码方式为LATIN1时, isLatin1才会返回真
byte coder() {
return COMPACT_STRINGS ? coder : UTF16;
}
byte[] value() {
return value;
}
private boolean isLatin1() {
return COMPACT_STRINGS && coder == LATIN1;
}
@Native static final byte LATIN1 = 0;
@Native static final byte UTF16 = 1;
构造方法
String中有很多的构造方法,比较重要的应该是这个String(char[] value, int off, int len, Void sig),因为很多构造方法其实就在调用这个
详细看一看
如果COMPACT_STRINGS设置为true,那么就看看value里面的字符是不是都可以被latin1编码,可以的话就是用Latin1编码,不然就用UTF16编码
String(char[] value, int off, int len, Void sig) {
if (len == 0) {
this.value = "".value;
this.coder = "".coder;
return;
}
if (COMPACT_STRINGS) {
byte[] val = StringUTF16.compress(value, off, len);
if (val != null) {
this.value = val;
this.coder = LATIN1;
return;
}
}
this.coder = UTF16;
this.value = StringUTF16.toBytes(value, off, len);
}
如果用字符串去构造只要复制一下属性就可以了
public String(String original) {
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}
其他api
length
返回value的长度,如果用的UTF16编码那么长度除以2,因为UTF16编码的长度是Latin1的2倍
public int length() {
return value.length >> coder();
}
isEmpty
就看看value数组长度是否为0
public boolean isEmpty() {
return value.length == 0;
}
equals
先看看是不是指向同一个地址,如果不是那么根据编码方式调用不同的比较函数,StringLatin1.equals的比较方式就是遍历byte数组,如果全部一样就是true,发现不一样的就返回false
public boolean equals(Object anObject) {
// 地址是否相同
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
// 根据编码调用不同的方法,具体方式是遍历byte数组看看是否相同
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
接下来的substring,startswith,replace,split,join,trim等具体方式就省略了,感觉代码里面对不同编码做了不同处理,其实要看主要过程的话,还是jdk8比较清晰。
换了个jdk结果发现里面的变化还挺大的,所以有事没事翻翻源码还是需要的~