String、StringBuffer和StringBuilder的区别
String、StringBuffer和StringBuilder的区别
下面从可变性、是否线程安全等方面来对String、StringBuffer、StringBuilder进行比较。
一、可变性
1. String
String 类中使用 final 关键字修饰字符数组来保存字符串。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[]; //... }
分析:我们知道被 final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。因此,final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因,因为这个数组保存的字符串是可变的(final 修饰引用类型变量的情况)。
String 真正不可变有下面几点原因:
1)保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
2)String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
2. StringBuffer
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。
二、线程是否安全
1. String
String在Java中是线程安全的。
原因:
1)不可变性:一旦创建,String 对象的内容就不能更改。
2)内存管理:Java 使用字符串常量池来优化内存使用。当多个 String 对象具有相同的值时,它们实际上会引用同一个对象。
在多线程环境中,多个线程可以安全地读取同一个 String 对象而无需同步。由于不可变性,不需要额外的同步机制来保护 String 对象,因此可以提高性能。
2. StringBuilder
StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
3. StringBuffer
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁(synchronized), 所以是线程安全的。这意味着在多线程环境中,StringBuffer 可以安全地被多个线程访问。
部分源码如下:
1 public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence 2 { 3 //... 4 @Override 5 public synchronized StringBuffer append(Object obj) { 6 toStringCache = null; 7 super.append(String.valueOf(obj)); 8 return this; 9 } 10 }
三、是否实现了equals和hashCode方法
1. String
String 类重写了 equals() 和 hashCode() 方法,使得两个 String 对象可以根据其内容进行比较。举个例子如下:
1 public class StringExample { 2 public static void main(String[] args) { 3 String str1 = new String("java"); 4 String str2 = new String("java"); 5 6 // 比较两个 String 对象的内容 7 System.out.println(str1.equals(str2)); // 输出: true 8 System.out.println(str1.hashCode() == str2.hashCode()); // 输出: true 9 } 10 }
2. StringBuilder 和 StringBuffer
StringBuilder 和 StringBuffer 没有实现 equals() 和 hashCode() 方法,使用时主要关注字符串的构建和修改。举个例子如下:
1 public class StringBufferExample { 2 public static void main(String[] args) { 3 StringBuffer sb1 = new StringBuffer("java"); 4 StringBuffer sb2 = new StringBuffer("java"); 5 6 // 比较两个 StringBuffer 对象的引用 7 System.out.println(sb1.equals(sb2)); // 输出: false 8 System.out.println(sb1.hashCode() == sb2.hashCode()); // 输出: false 9 } 10 }
如果需要比较内容,可以使用 toString() 方法将其转换为 String,然后进行比较。
四、字符串拼接用“+” 还是 StringBuilder?
Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的运算符。
String str1 = "he"; String str2 = "llo"; String str3 = "world"; String str4 = str1 + str2 + str3;
上面的代码对应的字节码如下:
可以看出,字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。
1 String[] arr = {"he", "llo", "world"}; 2 String s = ""; 3 for (int i = 0; i < arr.length; i++) { 4 s += arr[i]; 5 } 6 System.out.println(s);
StringBuilder 对象是在循环内部被创建的,这意味着每循环一次就会创建一个 StringBuilder 对象。
上面的代码对应的字节码如下:
如果直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。
1 String[] arr = {"he", "llo", "world"}; 2 StringBuilder s = new StringBuilder(); 3 for (String value : arr) { 4 s.append(value); 5 } 6 System.out.println(s);
上面的代码对应的字节码如下:
我们在平时写代码的时候,尽量避免多个字符串对象拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
不过,字符串使用 final 关键字声明之后,可以让编译器当做常量来处理。
五、总结
String:不可变,适合处理不可变、常量和不频繁修改的字符串场景,尤其在多线程环境中能够安全共享。
StringBuffer:可变,线程安全,适合多线程环境,性能较好。
StringBuilder:可变,非线程安全,性能最好,适合单线程环境。通常在需要频繁修改字符串时优先选择。
原文链接:https://javaguide.cn/java/basis/java-basic-questions-02.html