String类是不可变类,创建对象后,该对象的字符串是不可变的,直到该对象被销毁,但我们平时在用String的时候,String不是可以修改的吗?我们在修改String的字符串时,其实是整个对象都换了,修改后的String对象不再是原来的那个String对象。
String str = "123";
String str2 = str;
System.out.println(str == str2);
str = str + "";
System.out.println(str == str2);
运行结果:
这里==的比较的是对象的指向是否一样。可以看出,修改后String对象的指向发生了变化,这是因为String类的字符串修改的过程中,JVM把之前的那个String对象销毁了,然后又创建了一个新的String对象,对象名是一样的。
而StringBuffer和stringBuilder则是在原来的那个对象上修改。
StringBuilder stringBuilder = new StringBuilder("");
StringBuilder stringBuilder2 = stringBuilder;
System.out.println(stringBuilder == stringBuilder2);
stringBuilder.append("");
System.out.println(stringBuilder == stringBuilder2);
StringBuffer stringBuffer = new StringBuffer("");
StringBuffer stringBuffer2 = stringBuffer;
System.out.println(stringBuffer == stringBuffer2);
stringBuffer.append("");
System.out.println(stringBuffer == stringBuffer2);
运行结果:
下面我们再看一个示例,来比较这三个类的效率。
final int n = 20000;
long a = System.currentTimeMillis();
String str = "";
for (int i = 0; i < n; i++) {
str += i;
}
System.out.println("String的运行时间:" + (System.currentTimeMillis()-a)/1000.0 + "s");
long b = System.currentTimeMillis();
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < n; i++) {
stringBuffer.append(i);
}
System.out.println("StringBuffer的运行时间:" + (System.currentTimeMillis()-b)/1000.0 + "s");
long c = System.currentTimeMillis();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < n; i++) {
stringBuilder.append(i);
}
System.out.println("StringBuilder的运行时间:" + (System.currentTimeMillis()-c)/1000.0 + "s");
运行结果:
由于使用String类在修改数值的时候一直销毁创建,所以它花费的时间比StringBuilder和StringBuffer这两个多得多,而StringBuilder的运行效率比StringBuffer快,那是因为StringBuffer的底层加了synchronized的关键字,用来实现线程安全,因此StringBuilder的运行效率比StringBuffer快。我们用一个示例看一下StringBuffer和StringBuilder的线程安全问题。
StringBuffer:
StringBuffer str = new StringBuffer();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
str.append("a");
}
},String.valueOf(i)).start();
}
try {
TimeUnit.SECONDS.sleep(1);//等1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(str.length());
StringBuilder:
StringBuilder str = new StringBuilder();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
str.append("a");
}
},String.valueOf(i)).start();
}
try {
TimeUnit.SECONDS.sleep(1);//等1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(str.length());
前面那个返回的结果100000,跟预期的一样。后面返回的结果是94697,跟预期的不一样。这是因为StringBuffer类的append方法加了synchronized的关键字。
总结:
性能上,需要大量修改数值时,StringBuilder最快,StringBuffer次之,String最慢。
单线程下需要对字符串进行大量修改,应优先使用StringBuilder,多线程下需要对字符串进行大量修改,应优先使用StringBuffer,单线程下对字符串修改操作不多时,应优先使用String。