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

posted @ 2024-10-31 18:34  欢乐豆123  阅读(10)  评论(0编辑  收藏  举报