通读源码
1.类的定义
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
- final class
- 实现了Serializable 可序列化
- 实现了Comparable 可比较
- 实现了CharSequence 表明可读连续字符的特性
2.字段
/** The value is used for character storage. */
private final char value[];
存储字符的字符数组,也是final
/** Cache the hash code for the string */
private int hash; // Default to 0
缓存字符串的Hash值,默认为0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
序列化的id值
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
用于指定哪些字段需要被序列化
3.构造方法
public String() {
this.value = "".value;
}
空值构造,空值为 “”,为拷贝构造
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
传入一个String,同样是拷贝构造,同时拷贝了字符数组和hash值
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
通过字符数组构造,使用了数组拷贝的方法,拷贝构造
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
通过传入的字符数组的子串构造,同时传入起始的index和要复制的长度
- 检查offset的坐标是否合法,不合法抛出所以超出界限的异常,保障offset >= 0
- 检查count,小于0直接抛异常;等于0且offset在合法区间构造空值
- 注意这个Note,提示我们这两个值可能会接近Integer.MAX_VALUE(1的补码表示为00000000 00000000 00000000 00000001,-1则是1取反+1为11111111 11111111 11111111 11111111,>>>为无符号右移,高位补0,变成01111111 11111111 11111111 11111111,符号位为0,此数为最大的整数)
- 这里提示offset和count都有可能取最大整数,故而下面的判断需要写成 offset > value.length - count而不是offset + count > value.length,因为后者会可能导致整数溢出
- 这个判断同样实在检查合理性,是否存在offset和count选取的部分超出字符数组的情况
- 最后检查完毕,直接复制
public String(int[] codePoints, int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= codePoints.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
final int end = offset + count;
// Pass 1: Compute precise size of char[]
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
continue;
else if (Character.isValidCodePoint(c))
n++;
else throw new IllegalArgumentException(Integer.toString(c));
}
// Pass 2: Allocate and fill in char[]
final char[] v = new char[n];
for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
v[j] = (char)c;
else
Character.toSurrogates(c, v, j++);
}
this.value = v;
}
通过codePoints来构造字符串,codePoints指的是Unicode为世界上每一个字符赋予的一个编号,而这个编号编码成二进制的不同方式也就区分了像UTF8,UTF16,UTF32这样的编码集
除开上面讲到的判断,第一步,就是计算最终将那些BMP规范之外java字符之内的字符算进来之后的字符数组的总长度,第二步,就是区别的对待上述分出的这两部分字符,BMP范围内的直接强转,之外的通过特殊的方法赋值
private static void checkBounds(byte[] bytes, int offset, int length) {
if (length < 0)
throw new StringIndexOutOfBoundsException(length);
if (offset < 0)
throw new StringIndexOutOfBoundsException(offset);
if (offset > bytes.length - length)
throw new StringIndexOutOfBoundsException(offset + length);
}
补充一个工具方法,用于判断字节数组的offset length的合法性
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}
通过传入的字符集名称来队传入的字节数组进行编码,构造成一个字符串
先检查参数的合法性,首先字符集名称不能为空,然后是字节数组的两个参数不能越界,最后通过decode方法进行编码
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charset, bytes, offset, length);
}
此方法同上,只不过用来表示字符集的方式是通过Charset类而不是字符集的名字,有对应的decode方法处理
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
此方法同上,只不过是不选定返回,通过整个字节数组构造
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
同理
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
使用默认字符集,转换部分
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
使用默认字符集,转换全部
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
通过StringBuffer和StringBuilder构造字符串,通过这里可以看出,StringBuffer的toString方法是线程安全的,也正因为如此才会比StringBuilder的toString要慢一点,另一个刚好相反
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
这是一个protected修饰的方法,比String(char[])方法多了一个share,并由注释可以知道share为false不被支持,且在方法里面share参数没有被使用,故而可以知道这个参数的作用是为了和String(char[])方法做区分
我们回到方法实现,发现和没有share的方法的区别很明显,此方法是直接将传入的value的引用给当前的String的value,而没有share的方法则是通过Arrays.copyOf的方式进行拷贝
为什么会有这样一个方法的存在?
分析这个方法比之String(char[]) 方法的优点,主要体现在两个方面:
- 一个是占用的内存空间更少,因为这样构造的String的字符数组同传进来的数组共享同一片空间;
- 二个是直接赋值的方式要比一个个copy更快
而这个方法带来的影响也是很显然,这样的赋值方式其实有破坏String的不可变性的可能,至于不可变性的重要程度后续会谈,通过直接赋值的方式,如果传入的value变动,那么String就也会变,因此这个方法才不是public,在我们后续阅读的String部分方法中也会发现它的身影,到时候我们也会细谈
3.普通方法
public int length() {
return value.length;
}
返回字符串长度
public boolean isEmpty() {
return value.length == 0;
}
判断数组是否为空
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
获取字符串指定位置的字符,先判断index的合法性,合法返回
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}
获取指定索引的代码点,使用Character的独特方法获取
public int codePointBefore(int index) {
int i = index - 1;
if ((i < 0) || (i >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointBeforeImpl(value, index, 0);
}
获取前一个代码点
public int codePointCount(int beginIndex, int endIndex) {
if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
throw new IndexOutOfBoundsException();
}
return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
}
获取字符串的代码点数量
public int offsetByCodePoints(int index, int codePointOffset) {
if (index < 0 || index > value.length) {
throw new IndexOutOfBoundsException();
}
return Character.offsetByCodePointsImpl(value, 0, value.length,
index, codePointOffset);
}
获取给定索引的偏移量下的代码点
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
将字符串复制给从dstBegin处开始的dst字符数组的区域,没有边界检查
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
前一个方法的带边界检查的版本,从srcBegin到srcEnd -1 处的字符复制到dst从dstBegin开始的区域
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}
获取指定编码字符集名字的字符数组
public byte[] getBytes(Charset charset) {
if (charset == null) throw new NullPointerException();
return StringCoding.encode(charset, value, 0, value.length);
}
上一个方法的不同参数形式
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}
上述方法使用默认字符集
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
重点方法
首先,我们将自己同传入对象的引用进行比较,如果是同一块内存空间,返回相同,此时我们保证两者不是同一个对象
然后,判断传进来的对象是否是String(因为String不可以被继承),如果是继续后续判断,如果不是直接返回false,此时我们保证传入的对象是String这个类
之后,我们将传进来的对象显示转换为String
接下来的比较逻辑分为几步:
- 比较长度,相等就继续比较,不等直接返回false
- 然后获取两个string的字符数组,从末尾到开头逐一比较,全相同才是true
这种比较,虽然墨迹但可以使得大多数比较更为快速
private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
char v1[] = value;
char v2[] = sb.getValue();
int n = v1.length;
if (n != sb.length()) {
return false;
}
for (int i = 0; i < n; i++) {
if (v1[i] != v2[i]) {
return false;
}
}
return true;
}
这又是一个内部使用的方法,看代码就是一段简单的逐字符比较,逻辑同equals后半段相同,传入的对象要么是StringBuilder要么是StringBuffer(原因下一个会讲),且是非线程安全的
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
char v1[] = value;
int n = v1.length;
if (n != cs.length()) {
return false;
}
for (int i = 0; i < n; i++) {
if (v1[i] != cs.charAt(i)) {
return false;
}
}
return true;
}
见名知意,用于比较内容是否相等
首先为什么参数是CharSequence?
如果只有String这一个对象,上述的equals方法足够,因此方法主要是要考虑String与其他字符性质对象的内容比较
继续看之前,我们需要了解一下这几个类或者接口间关系
String,StringBuffer,StringBuilder都实现了CharSequence,然后AbstractStringBuilder有且仅有两个子类就是StringBuilder和StringBuffer
看代码
首先看是不是AbstractStringBuilder这一脉,实际上潜台词是是不是StringBuilder和StringBuffer之一(这俩也是final修饰的)
如果是,再看是不是StringBuffer,是就需要保证线程安全的比较,不是的话就只有可能是StringBuilder,就可以不用保证线程安全
如果不是,那再看是不是一个String,是的话就直接使用equals比较,
然后代码到这里,就是与非图中这三个的其他实现了CharSequence的类比较,比较思路同equals后半段一样
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
同样的见名知意,忽略大小写的比较,中i但看是怎么实现的,首先判断二者是不是同一个对象,是直接返回true,不是则继续比较,然后只要传进来的String为空或者二者长度不同都直接返回的是false,在前者保证的前提下,我们调用regionMathes方法比较,这个方法的比较过程,我们后续谈到该方法时再看。