JDK10源码阅读--String

时间:2021-11-20 13:29:45

jdk源码里对String的介绍:

String 是不可变的,一旦被创建其值不能被改变. String buffers 支持可变String.
因为String是不可变的, 所以它们可以被共享.
例如:

String str = "abc";

等价于

char data[] = {'a', 'b', 'c'};
String str = new String(data);

源码中提供的其他使用String的例子:

System.out.println("abc");
String cde = "cde";
System.out.println("abc" + cde);
String c = "abc".substring(2,3);
String d = cde.substring(1, 2);

  

String的方法包括检查字符串里的单个字符,比较字符串,搜索字符串,提取子字符串,创建字符串副本等.实例映射是基于Character中指定的Unicode标准.
Java语言为String的字符串连接符 + 提供特殊的支持, +也可以用于转换其他类型的对象为String.
除非另有说明,否则将null作为参数传递给构造方法或者方法会抛出NullPointerException异常.
String表示一个字符串通过UTF-16(unicode)格式,补充字符通过代理对(参见Character类的 Unicode Character Representations 获取更多的信息)表示。
索引值参考字符编码单元,所以补充字符在String中占两个位置。

1. String的定义

String不能被继承,因为String类有final修饰符.
同时String实现了3个接口:

  1. java.io.Serializable:String类可序列化(白话解释: 把原本在内存中的对象状态 变成可存储或传输的过程称之为序列化。序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上. )
  2. Comparable<String>:实现compareTo方法,String的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort )进行自动排序
  3. CharSequence:char值的一个可读字符序列.
    java
    public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence

2. String的属性

所有属性如下图:
JDK10源码阅读--String

value[]

@Stable
private final byte[] value;

  

value 用来存储字符.(记得jdk8的这个value还是 char数组 不是byte数组. 特意下jdk9看了下,发现jdk9已经是byte数组了.)
final关键字也可以看出,String一旦被初始化了就不能被更改.@Stable表示:表示此字段存储在字段中的第一个非null(相应,非零)值永远不会改变。( value不可能为null,从构造方法可以看出~ )

coder

private final byte coder;

codevalue中字节的编码标识符,现实中支持的是 LATIN1/UTF16

    static final byte LATIN1 = 0;
static final byte UTF16 = 1;

hash

private int hash;
缓存字符串的hashcode, 默认值为0

序列化serialVersionUID

private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];

String实现了Serializable接口,所以支持序列化和反序列化支持。Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。

COMPACT_STRINGS

static final boolean COMPACT_STRINGS;
static {
COMPACT_STRINGS = true;
}

字符串压缩,该字段值由JVM注入.

3. String的构造方法

String作为java中最长用到的类,有很多重载的构造方法,如下图:
JDK10源码阅读--String

无参构造方法

构造一个空字符串,值得注意的是:因为String是不可变的,所以不必要使用这个构造方法

public String() {
this.value = "".value;
this.coder = "".coder;
}

以String为参数

初始化新创建的String对象,与其参数相同.(其实就是参数字符串的副本)

public String(String original) {
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}

使用字符数组组构造String

public String(char value[]) {
this(value, 0, value.length, null);
}
String(char[] value, int off, int len, Void sig) {
if (len == 0) {
this.value = "".value;
this.coder = "".coder;
return;
}
if (COMPACT_STRINGS) {
// compressedCopy char[] -> byte[]
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);
}

String(char[] value, int off, int len, Void sig) 是私有构造方法 Void sig为了消除其他共有构造方法的歧义.
char[]值存储到byte[]中.如果char[] 中仅包含latin1字符,则每个字节代表相应字符的8个低位. 或者是由一个byte[]存储StringUTF16中定义的字节序列中的所有字符.
在使用字符数组来创建一个新的String对象的时候,不仅可以使用整个字符数组,也可以使用字符数组的一部分,只要多传入两个参数int offset和int count就可以了,如下:

public String(char value[], int offset, int count) {
this(value, offset, count, rangeCheck(value, offset, count));
}

offset是第一个字符的索引
count是子数组的长度.

通过字节数组构造String

JDK10源码阅读--String
jdk10中,String内部就是用byte[]存储,上图红框中的构造方法中大同小异,
都是以byte[]为参数,两个int分别是:其实位置和子byte[]长度.

参数byte[]保存到String的byte[]中需要再次进行解码.String参数或者Charset参数是指定的字符集.

    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;
}

如果没有指定字符集,默认使用ISO-8859-1进行解码.

    static Result decode(byte[] ba, int off, int len) {
String csn = Charset.defaultCharset().name();
try {
// use charset name decode() variant which provides caching.
return decode(csn, ba, off, len);
} catch (UnsupportedEncodingException x) {
warnUnsupportedCharset(csn);
}
try {
return decode("ISO-8859-1", ba, off, len);
} catch (UnsupportedEncodingException x) {
// If this code is hit during VM initialization, err(String) is
// the only way we will be able to get any kind of error message.
err("ISO-8859-1 charset not available: " + x.toString() + "\n");
// If we can not find ISO-8859-1 (a required encoding) then things
// are seriously wrong with the installation.
System.exit(1);
return null;
}
}

还有用StringBuilderStringBuffer作为参数构造String, 但是基本不这样用,因为StringBuilderStringBuffer都有.toString()方法.

4. String的一些方法

length

返回字符串的长度

public int length() {
return value.length >> coder();
}
isEmpty

判断字符串是否为空

public boolean isEmpty() {
return value.length == 0;
}
charAt

返回索引处的char值,范围为 0到length()-1,
序列第一位为0,下一位为1,依此类推

public char charAt(int index) {
if (isLatin1()) {
return StringLatin1.charAt(value, index);
} else {
return StringUTF16.charAt(value, index);
}
}
equals

字符串与指定对象比较,结果为true表示参数是String对象并且与此对象有相同的字符序列.

  public boolean equals(Object anObject) {
//同一个对象,为真
if (this == anObject) {
return true;
}
if (anObject instanceof String) {//String类型
String aString = (String)anObject;
if (coder() == aString.coder()) {//并且编码相同
//不同的编码,不同的比较方式
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
codePointAt

返回指定索引处的字符(Unicode code值.范围为 0到length()-1,
序列第一位为0,下一位为1,依此类推

public int codePointAt(int index) {
//latin1字符集
if (isLatin1()) {
checkIndex(index, value.length);
return value[index] & 0xff;
}
int length = value.length >> 1;
checkIndex(index, length);
// utf16字符集
return StringUTF16.codePointAt(value, index, length);
}
//例如:
System.out.println("A".codePointAt(0)); //结果为 65
offsetByCodePoints

通过Unicode code值返回索引偏移位置

public int offsetByCodePoints(int index, int codePointOffset) {
if (index < 0 || index > length()) {
throw new IndexOutOfBoundsException();
}
return Character.offsetByCodePoints(this, index, codePointOffset);
}
getChars

将此字符串中的字符复制到目标字符数组中.

/**
* @param srcBegin要复制的字符串中第一个字符的索引。
* @param srcEnd索引要复制的字符串中的最后一个字符。
* @param dst目标数组。
* @param dstBegin目标数组中的起始偏移量。
* */
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
checkBoundsBeginEnd(srcBegin, srcEnd, length());
checkBoundsOffCount(dstBegin, srcEnd - srcBegin, dst.length);
if (isLatin1()) {
StringLatin1.getChars(value, srcBegin, srcEnd, dst, dstBegin);
} else {
StringUTF16.getChars(value, srcBegin, srcEnd, dst, dstBegin);
}
}
getBytes

按照指定的字符集把String转成字节数组返回.

    public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, coder(), value);
}

参数也可以是java.nio.charset.Charset 或者无参使用默认字符集
JDK10源码阅读--String

contentEquals

字符串与指定的CharSequence进行比较,只有当此字符串与参数有相同的char值序列是返回true.
可以看到方法中根据参数的类型不同而采用不同的比较方法,AbstractStringBuilder,String 都实现了CharSequence接口.
这个方法包含了equals(String)方法,当参数类型为String时,复用equals方法.

    public boolean contentEquals(StringBuffer sb) {
return contentEquals((CharSequence)sb);
}
public boolean contentEquals(CharSequence cs) {
// Argument is a StringBuffer, StringBuilder
if (cs instanceof AbstractStringBuilder) {
if (cs instanceof StringBuffer) {
synchronized(cs) {
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
} else {
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
}
// Argument is a String
if (cs instanceof String) {
return equals(cs);
}
// Argument is a generic CharSequence
int n = cs.length();
if (n != length()) {
return false;
}
byte[] val = this.value;
if (isLatin1()) {
for (int i = 0; i < n; i++) {
if ((val[i] & 0xff) != cs.charAt(i)) {
return false;
}
}
} else {
if (!StringUTF16.contentEquals(val, cs, n)) {
return false;
}
}
return true;
}
comepareTo

按字典顺序比较两个字符串。比较基于字符串中每个字符的Unicode值.
如果当前String对象按照字典顺序在参数字符串之前,则结果为负整数.
反之为负整数.

public int compareTo(String anotherString) {
byte v1[] = value;
byte v2[] = anotherString.value;
if (coder() == anotherString.coder()) {
return isLatin1() ? StringLatin1.compareTo(v1, v2)
: StringUTF16.compareTo(v1, v2);
}
return isLatin1() ? StringLatin1.compareToUTF16(v1, v2)
: StringUTF16.compareToLatin1(v1, v2);
}
@param toffset此字符串中子区域的起始偏移量。
@param其他字符串参数。
@param ooffset字符串中子区域的起始偏移量
论据。
@param len要比较的字符数。
@return 如果指定该字符串的子区域完全匹配字符串参数的指定子区域返回true,否则返回false;
public boolean regionMatches(int toffset, String other, int ooffset, int len)

从索引toffset开始的当前字符串的子串 是否以制定的prefix前缀开头.

public boolean startsWith(String prefix, int toffset)

返回指定子字符串第一次出现的字符串中的索引。

public int indexOf(String str)

返回当前字符串的子串,从索引处开始直到字符串结尾.

public String substring(int beginIndex)

例如:
"unhappy".substring(2) 返回 "happy"
"Harbison".substring(3) 返回 "bison"
"emptiness".substring(9) 返回 "" (an empty string)

返回一个子字符串,子字符串从指定的beginIndex开始,并扩展到索引endIndex-1处的字符。 因此子串的长度是endIndex-beginIndex

    public String substring(int beginIndex, int endIndex) {
int length = length();
checkBoundsBeginEnd(beginIndex, endIndex, length);
int subLen = endIndex - beginIndex;
if (beginIndex == 0 && endIndex == length) {
return this;
}
return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
: StringUTF16.newString(value, beginIndex, subLen);
}


例如:
"hamburger".substring(4, 8) 返回 "urge"
"smiles".substring(1, 5) 返回 "mile"



将参数字符串连接到此字符串的末尾
public String concat(String str)

例如:
"cares".concat("s") 返回 "caress"
"to".concat("get").concat("her") 返回 "together"


返回替换所有出现的字符串.
如果当前字符串中未出现oldChar,则返回当前字符串.
`每次`出现的`oldChar` 都会被`newChar`替换.
public String replace(char oldChar, char newChar)

例如:
"the war of baronets".replace('r', 'y')
返回 "the way of bayonets"


判断此字符串是否与指定字符串匹配
public boolean matches(String regex)
 


把当前字符串按照`regex`参数拆分,返回一个`String`数组.
数组中的子串按它们在此字符串中出现的顺序排列.
如果匹配不到`regex`则数组中只有一个元素,就是该字符串.
limit 参数控制模式应用的次数,因此影响所得数组的长度。如果该限制 n 大于 0,则模式将被最多应用 n - 1 次,数组的长度将不会大于 n,而且数组的最后一项将包含所有超出最后匹配的定界符的输入。如果 n 为非正,那么模式将被应用尽可能多的次数,而且数组可以是任何长度。如果 n 为 0,那么模式将被应用尽可能多的次数,数组可以是任何长度,并且结尾空字符串将被丢弃。
public String[] split(String regex, int limit)

例如:
String ss = "1,,5,6";
String sss[] = ss.split(",",3);
System.out.println(sss.length);//返回数组长度3,后面的5,6会被合并为一项

把字符串所有字符转成(大写/小写) 返回新的字符串.

public String toLowerCase()
public String toUpperCase()


其他对象转成String

public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
public static String valueOf(char data[]) {
return new String(data);
}
public static String valueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
public static String valueOf(char c) {
if (COMPACT_STRINGS && StringLatin1.canEncode(c)) {
return new String(StringLatin1.toBytes(c), LATIN1);
}
return new String(StringUTF16.toBytes(c), UTF16);
}
public static String valueOf(int i) {
return Integer.toString(i);
}
public static String valueOf(long l) {
return Long.toString(l);
}
public static String valueOf(float f) {
return Float.toString(f);
}
public static String valueOf(double d) {
return Double.toString(d);
}

返回此字符串的hashCode.
public int hashCode()
hashcode计算公式:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

s[i]是string的第i个字符,n是String的长度。计算机的乘法涉及到移位计算,因为是二进制所以,当一个数乘以2时,就直接拿该数左移一位即可!选择31原因是因为31是一个素数!
在存储数据计算hash地址的时候,我们希望尽量减少有同样的hash地址,所谓“冲突”。如果使用相同hash地址的数据过多,那么这些数据所组成的hash链就更长,从而降低了查询效率!所以在选择系数的时候要选择尽量长的系数并且让乘法尽量不要溢出的系数,因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。
31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化,使用31的原因可能是为了更好的分配hash地址,并且31只占用5bits!
在java乘法中如果数字相乘过大会导致溢出的问题,从而导致数据的丢失.

最近公司和家里的事情都比较多,抽时间看了下String的源码,并不是非常的细致. 其实可以发现,因为String内部两种不同的编码集, 好多主要的代码都在StringLatin1中和StringUTF16中.