本系列是Java详解,专栏地址:Java源码分析
String 官方文档:String (Java Platform SE 8 )
String .java源码共3348行,下载地址见我的文章:Java源码下载和阅读(JDK1.8/Java 11)
文件地址:openjdk-jdk11u-jdk-11.0.6-3/src/java.base/share/classes/java/lang
想看String源码的主要原因是String是经常被问到的一个知识点,里面有很多实现有借鉴意义。
看源码的第一步是看注释。
1.String是常量,创建后不可更改。String buffer
支持可变的String
。
2.如果给构造函数传递null
,那么会抛出异常NullPointerException
。
3.String是UTF-16
格式的字符串。
4.index,索引指的是char的code unit
。
5.
6.
实现了接口:Serializable
和CharSequence
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
使用byte数组存储数据,final
关键字表示是不可变的,String在创建后就不可修改。
import jdk.internal.vm.annotation.Stable;
@Stable
private final byte[] value;
根据注释:如果String
示例是常量,那么会发生常量折叠constant folding
。
注意,这里有个注解@Stable
。
如果存在注解@Stable
,HotSpot
虚拟机可以做优化,如constant folding
。处理final fields
就像处理static final fields
。
该注解仅在boot loader
加载类的时候起作用,其他时候不起作用。
因为使用byte数组来存储数据,所以需要指定字符串的编码,支持UTF-16
和LATIN1
。
/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
* LATIN1
* UTF16
*/
private final byte coder;
@Native static final byte LATIN1 = 0;
@Native static final byte UTF16 = 1;
空的构造函数还是挺新奇的。支持从String
,char []
,byte []
,int []
,StringBuffer
,StringBuilder
进行构造。
其中int []
是Unicode code point
。
byte []
可以指定编码,包括UTF_8
,ISO_8859_1
,US_ASCII
.
public String() {
this.value = "".value;
this.coder = "".coder;
}
@HotSpotIntrinsicCandidate
public String(String original) {
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}
public String(char value[]) {
this(value, 0, value.length, null);
}
public String(char value[], int offset, int count) {
this(value, offset, count, rangeCheck(value, offset, count));
}
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
checkBoundsOffCount(offset, length, bytes.length);
StringCoding.Result ret =
StringCoding.decode(charsetName, bytes, offset, length);
this.value = ret.value;
this.coder = ret.coder;
}
.....................
可以看出不同的编码格式调用的是不同的函数。
public char charAt(int index) {
if (isLatin1()) {
return StringLatin1.charAt(value, index);
} else {
return StringUTF16.charAt(value, index);
}
}
public static char StringLatin1::charAt(byte[] value, int index) {
if (index < 0 || index >= value.length) {
throw new StringIndexOutOfBoundsException(index);
}
return (char)(value[index] & 0xff);
}
public static char StringUTF16::charAt(byte[] value, int index) {
checkIndex(index, value);
return getChar(value, index);
}
@HotSpotIntrinsicCandidate
// intrinsic performs no bounds checks
static char StringUTF16::getChar(byte[] val, int index) {
assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
index <<= 1;
return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
((val[index] & 0xff) << LO_BYTE_SHIFT));
}
然后我们可以看到一个非常有趣的代码:
return (char)(value[index] & 0xff);
这里有几个知识点:
1.常见的ASCII是从0到126,包括大小写字母和常用符号。但一个byte
是一个字节,有8位,最高位为0空着有些浪费,因此有了Extended ASCII
。
2.Java对字符采用的编码是LATIN1
,也称ISO 8859-1
,ISO/IEC 8859-1:1998
,Latin-1
。采用这个编码的原因是ISO-8859-1
对应于ISO/IEC 10646
即Unicode
的前256个码位。
3.因此对于最高位为1的byte
来说,在转换为char
的过程中最高位会置1(补码),因此需要让最高位永远置0,所以要与上0xff
。
下面用实例说明,Extended ASCII
的165,也就是1010 0101
是人民币货币符号¥。
System.out.println((char) ((byte) 165 & 0xff));
System.out.println((char) (byte) 165);
输出结果如下:
¥
ᆬ
String实现了equals
,具体做法也就是从头比到尾。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
@HotSpotIntrinsicCandidate
public static boolean StringLatin1::equals(byte[] value, byte[] other) {
if (value.length == other.length) {
for (int i = 0; i < value.length; i++) {
if (value[i] != other[i]) {
return false;
}
}
return true;
}
return false;
}
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
hash = h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
}
return h;
}
public static int StringLatin1::hashCode(byte[] value) {
int h = 0;
for (byte v : value) {
h = 31 * h + (v & 0xff);
}
return h;
}
& 0xff
的原因之前提过了
public int indexOf(String str) {
if (coder() == str.coder()) {
return isLatin1() ? StringLatin1.indexOf(value, str.value)
: StringUTF16.indexOf(value, str.value);
}
if (coder() == LATIN1) { // str.coder == UTF16
return -1;
}
return StringUTF16.indexOfLatin1(value, str.value);
}
@HotSpotIntrinsicCandidate
public static int StringLatin1::indexOf(byte[] value, int valueCount, byte[] str, int strCount, int fromIndex) {
byte first = str[0];
int max = (valueCount - strCount);
for (int i = fromIndex; i <= max; i++) {
// Look for first character.
if (value[i] != first) {
while (++i <= max && value[i] != first);
}
// Found first character, now look at the rest of value
if (i <= max) {
int j = i + 1;
int end = j + strCount - 1;
for (int k = 1; j < end && value[j] == str[k]; j++, k++);
if (j == end) {
// Found whole string.
return i;
}
}
}
return -1;
}
发现并没有做优化。
intern函数经常会在面试中出现,是native
函数。
public native String intern();
intern函数的意义:如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。
根据注释:s.intern() == t.intern()
的结果为true
当且仅当s.equals(t)
的结果为true
。
有几个注意事项:
1.所有的字符串常量的字符串都在字符串常量池中。
2.intern
函数的意义是如果你new String
,那么对象会被放在堆中,并且是新的对象。如果你知道会相同的字符串会用到多次,那么可以使用intern
函数,使其进入字符串常量池,减少空间的开销。
3.字符串常量池在java 7后就存在堆中。在JDK 7中,不再将永久字符串分配给Java堆的永久代,而是分配给Java堆的主要部分(称为年轻代和旧代) 。此更改将导致更多数据驻留在主Java堆中,而永久生成中的数据更少,因此可能需要调整堆大小。
4.字符串如何加入字符串常量池,在Java 7 以后,new
的对象加入的都是引用。
public static void main(String[] args) {
String str2 = new String("str") + new String("01");
str2.intern();
String str1 = "str01";
System.out.println(str2 == str1); //true
String s4 = new String("1");
s4.intern();
String s5 = "1";
System.out.println(s4 == s5); //false
String s = new StringBuilder("aa").append("bb").toString();
String s2 = new StringBuilder("cc").toString();
System.out.println(s.intern() == s); //true
System.out.println(s2.intern() == s2); //false
}
参考:
其他函数并没有值得说的部分。