一、String,StringBuffer,StringBuilder
这两个作为我们日常开发中非常常用的类,你真的了解吗?
1.String
String类是不可变类,一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
{
/** The value is used for character storage. */
private final char value[];
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
}
上面是String类中定义的部分,我们可以从中看出一些东西:
首先是整个类是被final修饰的,说明整个String类都不可以被其他的类继承,同时它的所有成员方法默认都是final的,这实际上也避免了有开发者想要继承String类最后导致的出现歧义的问题;
其次value[]变量说明任何的String其实都是通过一个char数组来进行保存,并且同理不可扩张;
接着是一些String实现的方法:
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
char buf[] = new char[count + otherLen];
getChars(0, count, buf, 0);
str.getChars(0, otherLen, buf, count);
return new String(0, count + otherLen, buf);
}
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = count;
int i = -1;
char[] val = value; /* avoid getfield opcode */
int off = offset; /* avoid getfield opcode */
while (++i < len) {
if (val[off + i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0 ; j < i ; j++) {
buf[j] = val[off+j];
}
while (i < len) {
char c = val[off + i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(0, len, buf);
}
}
return this;
}
通过这些方法我们不难发现在这些方法最后返回的时候全部都是new了一个新的String对象返回,原来的String对象并没有发生任何的变化,也就是说明任何的关于String的copy或者sub方法是截取原字符串类似的说法都是错的(String不可变!String不可变!String不可变!)
下一个需要提的点应该是在很多的面试中会出现的点:我现在写一个语句String a = "123",那么这句代码一定会新创建一个String对象么?或者说在这句话执行完我创建了几个对象呢?这个问题也就引出了一个结论,使用String不一定会创建对象!
当我们在使用上面这段代码的时候,JVM会首先去常量池里面检查有没有这个常量,如果有的话会直接返回这个实例的引用,否则的话就创建一个新的实例的引用放进常量池中。
所以!在直接使用双引号创建String对象的时候不一定会创建一个新的String对象,有可能只是指向了一个原来的引用而已!只有使用new关键字创建出来的String才是真的创建了一个新的对象
那么现在我们知道使用new()一定能够创建一个新的String对象,假设现在有语句String a = new String("123");执行的过程是怎样的呢?
答案是:首先走常量池的路线取到一个实例的引用,然后在堆上创建一个新的String实例,走以下构造函数给value属性赋值,然后把实例引用赋值给a:
public String(String original) {
int size = original.count;
char[] originalValue = original.value;
char[] v;
if (originalValue.length > size) {
// The array representing the String is bigger than the new
// String itself. Perhaps this constructor is being called
// in order to trim the baggage, so make a copy of the array.
int off = original.offset;
v = Arrays.copyOfRange(originalValue, off, off+size);
} else {
// The array representing the String is the same
// size as the String, so no point in making a copy.
v = originalValue;
}
this.offset = 0;
this.count = size;
this.value = v;
}
这里我们可以发现,new出来的这个String实际上并没有new一个新的char数组来存放新的字符串,现在的value依旧是等于原来常量池中的那个实例的value(理解起来可能有些绕,但实际还是那个观点,String是final的,只要你要一个新的String一定会存在new一个新的,但是其中真的用来存放字符串的char数组同样可以采用另一个String的引用,也就是说外层是String,内层是char数组。可能不太准确,但是个人感觉这样会好理解一些)
2.StringBuffer与StringBulider
首先StringBuffer与StringBuilder都继承自AbstractStringBuilder,其中StringBuffer是线程安全的,因为它的源码里有很多的synchronized,这个就不多说了。这里简单看看AbstractStringBuilder的实现原理:AbstractStringBuilder中采用一个char数组来保存需要append的字符串,char数组有一个初始大小,当append的字符串长度超过当前char数组容量时,则对char数组进行动态扩展,也即重新申请一段更大的内存空间,然后将当前char数组拷贝到新的位置,因为重新分配内存并拷贝的开销比较大,所以每次重新申请内存空间都是采用申请大于当前需要的内存空间的方式,默认是两倍。
ps:如果有问题问为什么需要采用StringBuffer(StringBuilder)来做字符串的拼接,直接回答直接采用+进行拼接的效率差,因为在使用+的过程中JVM会创建多个String对象,造成额外的开销;
从 JDK 1.5 开始,带有字符串变量的连接操作(+),JVM 内部采用的是 StringBuilder 来实现的,而之前这个操作是采用 StringBuffer 实现的。