在java中,String是一个被声明为final的类,它所保存的字符串是一个常量,一旦定义不可改变(final类型,且是数组,数组一旦定义了,其长度便是不可变,是一块连续的内存地址)
private final char value[];
所以String是一个指向常量字符串的引用,其大小是不可改变的,所以当给String赋上新值时,会改变该引用所指向的新的字符串,而不会改变原来的字符串。所以当一个String类经常被修改时,会产生很多的垃圾,浪费内存空间,同时也会创建新的字符串,所以效率也很低,在这样的背景下便有了StringBuilder和StringBuffer。
StringBuilder和StringBuffer类似,只不过前者是非线程安全,而后者是线程安全的,两者都是用于保存可变的字符串。里面放置了一个默认大小为16的字符数组,当要追加的字符串加上原有的字符串的长度大于这个存储数组的长度时便会自动扩充数组长度,以便容纳要追加的内容(两倍大小扩充)。
接下来,便浅析String在是使用“+”操作符时的一些细节,以便能更好的理解为什么String效率会比较低。
public class Test {
public static void main(String[] args) {
String s1 = "1";
String s2 = "1";
System.out.println(s1 == s2);
}
}
打印结果为true,这说明了s1和s2都指向了同一块地址,也就是保存字符串a的字符数组的地址
public class Test {
public static void main(String[] args) {
String s1 = "1";
String s2 = "1";
s1 = new String("1");
System.out.println(s1 == s2);
}
}
打印结果为false,这说明了s1已指向了一块新的内存地址,所以打印出来的是false
public class Test {
public static void main(String[] args) {
// String s1 = "1";
// String s2 = "1";
// s1 = new String("1");
// System.out.println(s1 == s2);
String s3 = "1" + "0";
String s4 = "1" + "0";
System.out.println(s3 == s4);
String s5 = "1";
String s6 = "0";
String s7 = s5 + s6;
System.out.println(s3 == s7);
String s8 = s5 + "0";
System.out.println(s3 == s8);
}
}
打印出来的结果为:
true
false
false
这时因为编译器做了手脚,它帮我们优化了String在拼接时的一些操作。可以通过javap -c xxx进行反编译来查看编译过程:
首先编译器自动的把字符串1和0合并成为10,而不是创建了一个1和一个0
所以s3和s4指向了同一块内存地址,这块地址保存的是10
其次当用s7用s5和s6拼接时,编译器自动创建了一个StringBuillder类,然后调用它的append方法来进行拼接,最后在调用toString返回给s7
s8的过程和s7类似
这也就解释了后面两个打印出来的结果为什么是false了,他们都指向由StringBuillder创建出来的String的内存地址,而不是s3和s4所指向的地址。
所以当用变量对String进行拼接时都会产生一个StringBuillder类来进行拼接,对于效率要求高的程序来说需要注意这点。