Java中的String类非常重要,结合网上的资料以及自己分析String类的源码,做如下总结:
String类对象是不可变的,它可以被共享。
1.String类开始是这样定义的:
public final class String implements java.io.Serializable,Comparable<String>,CharSequence
从以上定义可以看出,String类实现了三个接口,且分别要实现他们的方法。
2.定义了如下成员变量:
private final char value[];
private int hash;
private static final long serialVersionUID =-6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
可以看出,String底层还是用数组来存储数据的,hash值是当使用了hashTable或者hashMap数据结构时,才会使用。后面两个变量是关于序列化的。
3.定义了如下构造方法:
(1)使用无参构造函数
public String(){
this.value = "".value;
}
代表空字符序列,使用这个构造函数是不必要的,因为字符串对象是不可变的。这个默认的空字符串会一直存在内存中。
(2)使用字符串对象来初始化
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
新创建的实例与形参的实例中的value和hash值是相同的,说明两个value都指向了同一块内存中的数据(数组的数据)。随后修改字符串会影响原来的值。
(3)使用了数组对象来初始化
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
Arrays.copyOf()方法中会在堆中重新申请一块内存,然后将形参中value的值复制到该新的内存中,并且将这个地址值返回给当前对象的value。形参数组的后续修改不会影响该对象的改变。
(4)截取源字符数组中的部分子字符数组来初始化
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);
}
(5)截取整型数组中的部分子数组来初始化
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;
}
分配一个新的数组,
其中包含一个子阵列中的字符
Unicode代码点,
数组的后续修改不会影响所创建的字符串对象。
(6)使用StringBuffer对象来初始化
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
StringBuffer是线程安全的,所以需要加synchronized。这个用的是Arrays.copyOf()方法,分配新的空间,将buffer的序列值赋值给新空间中。说明后续数组的改变不影响新创建的字符串对象。
(7)使用StringBuilder对象来初始化
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
StringBuilder是线程不安全的。
(8)用字符数组初始化
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
包私有构造函数。这个构造函数总是被预期用share == true调用。需要一个单独的构造函数,因为我们已经有一个public了。