导读:当提到java中的String,相信所有人第一时间想到的便是String非基本数据类型及其不可变性,可是String是怎么实现的不可变性呢?
String
当浏览String类的注释时,可以获取几个信息:1.java中所有的字符串都是该类的实例。2.String是不可变的,3.String类主要提供了一些对于字符串序列的操作。4.java提供对string连接的特殊操作(StringBuffer StringBuilder实现)以及其他对象向String的转换。
在正式看源码的时候最引人注意的便是下列一行代码:
private final char value[];
通过注解可以的是String便是用这char数组来存放字符的,惊喜不?!
思考:既然String类使用char数组来实现的,那么不可变性又是怎么确定的呢?
忽略接下来的hash及序列化相关的代码之后的便是String类的一些构造方法,其中最关键的便是下边的一个构造方法:
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
由代码可知当创建一个String时,直接生成一个新的char数组,而数组的大小为新的String字符的个数,这样来保证String的不可变形?相信大家会问这个跟不可变有什么关系?那我们假设:
this.value = value;
那么新生成的String实例与传入的value数组便形成以用,当vaule数组改变是String也随之改变。
其他的构造方法就不讲了,其实现的效果和这个构造方法是一样的,都是通过创建一个新的value数组来实现。
由于String默认的是UTF8编码,因此有些构造方法中也可以设置编码,这是会通过StringCoding类来实现编码解码,在此就不说这个了。
之后我们可以看下边的这个方法:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
通过生成新的String对象来确保String的不可变性。
public native String intern();
由该方法注释可知,String类维护了一个string的缓冲池,在最开始的时候该缓冲池是空的,当调用该方法是,如果缓冲池里没有该字符串的话便将字符串放进缓冲池里并返回一个引用,如果该字符串存在的话就直接返回该字符串的引用。
该方法涉及的一些其他问题在这里就先不讨论。
StringBuilder&StringBuffer
由于StringBuilder与StringBuffer都继承了AbstractStringBuilder,StringBuffer在主要方法上增加synchronized关键字来保证同步,并且其主要方法都是都是在AbstractStringBuilder中定义,因此只需解读AbstractStringBuilder即可。AbstractStringBuilder是一个可以扩展的String对象,其数据结构也是char数组。
在AbstractStringBuilder中默认的构造方法中并没有规定char数组的大小,但是在StringBuilder中默认设置value数组长度为16,在有参构造方法中,将参数的长度加上16来生成value数组,并且还专门提供了可以设置数组长度的构造方法,由于StringBuilder中的元素个数小于value数组的大小,因此可认为StringBuilder为带缓冲的String对象。在AbstractStringBuilder中提供了lengeh()和capacity()两个方法,前者返回的是value数组中数据的个数,通过count字段来计数,后者返回value数组的大小。
当value数组的大小无法满足需求的时候,有下边的方法来进行扩容:
//minimumCapacity为放下所有的新元素所需要的最小的长度
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
//设置value数组的新长度为原有长度的2倍加2
//此时如果还是无法满足需求的话,则将长度设为minimumCapacity
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
//这段代码没太看懂
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
//生成新的数组并将原有数组中的内容复制到新的数组中
value = Arrays.copyOf(value, newCapacity);
}
除了实现可扩容,StringBuilder提供的其他方法与String无异,不过StringBuilder并没有提供intern()方法。
不过在StringBuffer中,还提供了一个数组:
private transient char[] toStringCache;
由注解可知,此数组存放的是上次toString()时,输出的值,并且在调用append()等可能改变原有value数组内容的方法时将该数组清空。
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
但是我实在没看懂他有什么用,因为在StringBuffer中是synchronized关键字都是加在方法上的,因此并不会节约太多时间,
也可能是JDK的编写者觉得copyOfRange这个方法太费时间了。。。那为什么在StringBuilder没有这么做呢?