一、区别

字符串拼接尽量使用 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:线程不安全

  1. public static void main(String[] args) {
  2. String str = "";
  3. StringBuilder stringBuilder = new StringBuilder();
  4. StringBuffer stringBuffer = new StringBuffer();
  5. long start;
  6. long end;
  7. start = System.currentTimeMillis();
  8. for (int i = 0; i < 99999; i++) {
  9. str = str + "a";
  10. }
  11. end = System.currentTimeMillis();
  12. System.out.println("使用string的时间是:" + (end - start) + "毫秒!");
  13. start = System.currentTimeMillis();
  14. for (int i = 0; i < 99999; i++) {
  15. stringBuffer.append("a");
  16. }
  17. end = System.currentTimeMillis();
  18. System.out.println("使用stringBuffer的时间是:" + (end - start) + "毫秒!");
  19. start = System.currentTimeMillis();
  20. for (int i = 0; i < 99999; i++) {
  21. stringBuilder.append("a");
  22. }
  23. end = System.currentTimeMillis();
  24. System.out.println("使用StringBuilder的时间是:" + (end - start) + "毫秒!");
  25. }
  1. // 结果
  2. 使用string的时间是:2126毫秒!
  3. 使用stringBuffer的时间是:3毫秒!
  4. 使用StringBuilder的时间是:2毫秒!

注意:当使用 append()时,如果是添加一个已知的字符,建议使用单引号,如:append(‘a’),而非append(“a”),使用单引号性能更好。

通过如下代码也可以看出String的方法返回的都是新的String,new String(),而StringBuilder方法返回的大都是this即自己。所以String操作会产生新的对象,这也会消耗性能。

  1. // StringBuilder 的方法
  2. @Override
  3. public StringBuilder append(String str) {
  4. super.append(str);
  5. return this;
  6. }
  7. // String的方法
  8. public String concat(String str) {
  9. int otherLen = str.length();
  10. if (otherLen == 0) {
  11. return this;
  12. }
  13. int len = value.length;
  14. char buf[] = Arrays.copyOf(value, len + otherLen);
  15. str.getChars(buf, len);
  16. return new String(buf, true);
  17. }

StringBuilder和StringBuffer的源码, 我们发现这两者都继承自AbstractStringBuilder类, 通过查看该类的源码, 得知StringBuilder和StringBuffer两个类也是通过char类型数组实现的。

二、线程安全

String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、 append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同 步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

  1. @Override
  2. public synchronized StringBuffer append(Object obj) {
  3. toStringCache = null;
  4. super.append(String.valueOf(obj));
  5. return this;
  6. }
  1. @Override
  2. public StringBuilder append(String str) {
  3. super.append(str);
  4. return this;
  5. }

三、String底层

  1. public final class String
  2. implements java.io.Serializable, Comparable<String>, CharSequence {
  3. private final char value[];
  4. private int hash; // Default to 0
  5. private static final long serialVersionUID = -6849794470754667710L;
  6. ......此处省略N多代码
  7. /**
  8. * Initializes a newly created {@code String} object so that it represents
  9. * the same sequence of characters as the argument; in other words, the
  10. * newly created string is a copy of the argument string. Unless an
  11. * explicit copy of {@code original} is needed, use of this constructor is
  12. * unnecessary since Strings are immutable.
  13. *
  14. * @param original
  15. * A {@code String}
  16. */
  17. public String(String original) {
  18. this.value = original.value;
  19. this.hash = original.hash;
  20. }
  21. }

String类是final修饰的,然后还有个成员属性 value 它也是final的并且是一个char类型的数组
通过构造方法可以看到,我们传递的参数值 是直接赋值给了 value

String它是一个不可变字符串,底层是一个char类型的数组

实际上我们经常 这样写 String a = “abc”; 这个时候 声明的变量值是在常量池中的。

  1. String str1 = a”; String str2 = b”; str1 = str1 + str2

我们是可以使用 “+” 来拼接字符串,不过 这里 jdk的虚拟机是做了优化的,并不是表面看到的使用 连接符 对原来的String做了拼接,而是使用了StringBuilder的 append()方法进行字符串的拼接,最后用toString方法返回拼接后的结果存到s3中,最后s3赋值给str1。

三、总结

  1. 操作少量的数据: 适用 String
  2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
  3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer