一、区别
字符串拼接尽量使用 StringBuffer 的append方法来拼接。而直接使用”+”来连接String类型。会增加内存和CPU的开销。String字符串拼接的原理如下 String str1 = “a”; String str2 = “b”; str1 = str1 + str2; 内存上,他先会开辟出一个新的内存空间,存放str3 = str1+str2,然后再把str3的引用交给str1.如果使用StringBuffer呢?则是在str1后面“接”上的,完成过程只有str1,str2两个对象。
相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提 升,但却要冒多线程不安全的⻛险。
StringBuffer:线程安全
StringBuilder:线程不安全
public static void main(String[] args) {
String str = "";
StringBuilder stringBuilder = new StringBuilder();
StringBuffer stringBuffer = new StringBuffer();
long start;
long end;
start = System.currentTimeMillis();
for (int i = 0; i < 99999; i++) {
str = str + "a";
}
end = System.currentTimeMillis();
System.out.println("使用string的时间是:" + (end - start) + "毫秒!");
start = System.currentTimeMillis();
for (int i = 0; i < 99999; i++) {
stringBuffer.append("a");
}
end = System.currentTimeMillis();
System.out.println("使用stringBuffer的时间是:" + (end - start) + "毫秒!");
start = System.currentTimeMillis();
for (int i = 0; i < 99999; i++) {
stringBuilder.append("a");
}
end = System.currentTimeMillis();
System.out.println("使用StringBuilder的时间是:" + (end - start) + "毫秒!");
}
// 结果
使用string的时间是:2126毫秒!
使用stringBuffer的时间是:3毫秒!
使用StringBuilder的时间是:2毫秒!
注意:当使用 append()时,如果是添加一个已知的字符,建议使用单引号,如:append(‘a’),而非append(“a”),使用单引号性能更好。
通过如下代码也可以看出String的方法返回的都是新的String,new String(),而StringBuilder方法返回的大都是this即自己。所以String操作会产生新的对象,这也会消耗性能。
// StringBuilder 的方法
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
// String的方法
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);
}
StringBuilder和StringBuffer的源码, 我们发现这两者都继承自AbstractStringBuilder类, 通过查看该类的源码, 得知StringBuilder和StringBuffer两个类也是通过char类型数组实现的。
二、线程安全
String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、 append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同 步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
三、String底层
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private int hash; // Default to 0
private static final long serialVersionUID = -6849794470754667710L;
......此处省略N多代码
/**
* Initializes a newly created {@code String} object so that it represents
* the same sequence of characters as the argument; in other words, the
* newly created string is a copy of the argument string. Unless an
* explicit copy of {@code original} is needed, use of this constructor is
* unnecessary since Strings are immutable.
*
* @param original
* A {@code String}
*/
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
}
String类是final修饰的,然后还有个成员属性 value 它也是final的并且是一个char类型的数组
通过构造方法可以看到,我们传递的参数值 是直接赋值给了 value
String它是一个不可变字符串,底层是一个char类型的数组
实际上我们经常 这样写 String a = “abc”; 这个时候 声明的变量值是在常量池中的。
String str1 = “a”; String str2 = “b”; str1 = str1 + str2
我们是可以使用 “+” 来拼接字符串,不过 这里 jdk的虚拟机是做了优化的,并不是表面看到的使用 连接符 对原来的String做了拼接,而是使用了StringBuilder的 append()方法进行字符串的拼接,最后用toString方法返回拼接后的结果存到s3中,最后s3赋值给str1。
三、总结
- 操作少量的数据: 适用 String
- 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer