字符串的不可变性
定义一个字符串
String s = "abcd";

使用变量赋值
String s2 = s;

字符串连接
s = s.contact("ef");

可以看出,String对象一旦被创建出来,就无法修改。如果需要一个可修改的字符串,应该使用StringBuffer或者StringBuilder,否则会有大量时间浪费在垃圾回收上。
编译器对“+”的优化
- 对于String s = "a" + "b",编译后变成String s = "ab"。
- 对于String s = "a" + 变量,编译后用StringBuilder.append()方法替代,最后调用toString()方法。
字符串拼接的几种方式
- 使用“+”拼接
String str = "str"; str += String.valueOf(i);使用“+”拼接,实际上是用StringBuilder.append()。
- String.concat()
String str = "str"; str = str.concat(String.valueOf(i));String.contact实现的原理是,创建一个字符数组,长度是已有字符串和待拼接字符串的长度之和,再把两个字符串的值复制到新的字符数组中,并使用这个字符数组创建一个新的String对象并返回。简单地说就是new了一个新的String对象。
- StringBuilder.append()
StringBuilder builder = new StringBuilder("str"); builder.append(String.valueOf(i));StringBuilder.append实现的原理是,StringBuilder内部也有一个char数组,但不是final的,进行append时,会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩展。
- StringBuffer.append()
StringBuffer buffer = new StringBuffer("str"); buffer.append(String.valueOf(i));StringBuffer.append和StringBuilder.append原理是一样的,不同的是,StringBuffer的append方法是用synchronized修饰的,是线程安全的。
- String.join()
String str = "str"; str = String.join("", str, String.valueOf(i));String.join内部也是用StringBuilder来实现的。
字符串拼接性能大比拼
测试代码都类似下面这段:
public void stringJoint1() {
long t1 = System.currentTimeMillis();
String str = "str";
for (int i = 0; i < 50000; i ++) {
str += String.valueOf(i);
}
long t2 = System.currentTimeMillis();
System.out.println("+ cost: " + (t2 - t1));
}
运行结果:
String.concat cost: 1902
StringBuilder cost: 6
StringBuffer cost: 5
+ cost: 5202
String.join cost: 5298
多次运行之后,可以观察到大概的运行效率是:
StringBuilder ≈ StringBuffer < String.contact < String.join ≈ "+"
- StringBuffer相对StringBuilder多了同步的操作,所以在单线程环境下运行效率差不多;
- String.contact每次都会创建新的字符串,所以会更慢一些;
- String.join和“+”每次都会创建StringBuilder对象所以更慢。
总结一下:
- 如果不在循环体中拼接字符串,直接使用+就可以。
- 如果在循环体中拼接字符串,非并发时使用StringBuilder,并发时使用StringBuffer。