一、区别
字符串拼接尽量使用 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 的方法@Overridepublic 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 并没有对方法进行加同步锁,所以是非线程安全的。
@Overridepublic synchronized StringBuffer append(Object obj) {toStringCache = null;super.append(String.valueOf(obj));return this;}
@Overridepublic StringBuilder append(String str) {super.append(str);return this;}
三、String底层
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {private final char value[];private int hash; // Default to 0private 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
