0
点赞
收藏
分享

微信扫一扫

linux查看系统负载情况

进击的铁雾 2024-11-06 阅读 24

String


String、StringBuffer、StringBuilder 的别?

 String

  • 不可变String对象一旦创建,其内容就不能改变。每次对String的操作(如拼接、替换等)都会生成新的String对象。
  • 线程安全:由于不可变性,String是线程安全的。
  • 适用场景:适用于不需要频繁修改字符串内容的场景。

StringBuffer

  • 可变StringBuffer对象的内容可以修改,且提供了很多用于修改字符串内容的方法(如append()insert()delete()等)。
  • 线程安全StringBuffer的方法使用了同步机制,因此是线程安全的。
  • 适用场景:适用于多线程环境中需要频繁修改字符串内容的场景。

 StringBuilder

  • 可变StringBuilderStringBuffer类似,也是可变的。
  • 非线程安全StringBuilder的方法没有使用同步机制,因此不是线程安全的。
  • 适用场景:适用于单线程环境中需要频繁修改字符串内容的场景,性能通常比StringBuffer更高。
public class StringExamples {  
    public static void main(String[] args) {  
        // String 示例  
        String str = "Hello";  
        // str = str + " World"; // 每次拼接都会生成新的String对象  
        String concatenatedStr = str.concat(" World"); // 使用concat方法进行拼接  
        System.out.println("String: " + concatenatedStr);  
  
        // StringBuffer 示例  
        StringBuffer stringBuffer = new StringBuffer("Hello");  
        stringBuffer.append(" World");  
        System.out.println("StringBuffer: " + stringBuffer.toString());  
  
        // StringBuilder 示例  
        StringBuilder stringBuilder = new StringBuilder("Hello");  
        stringBuilder.append(" World");  
        System.out.println("StringBuilder: " + stringBuilder.toString());  
  
        // 线程安全性示例(仅示意,不建议在生产代码中这样测试)  
        Runnable task = () -> {  
            for (int i = 0; i < 1000; i++) {  
                // StringBuffer 是线程安全的  
                stringBuffer.append(i);  
                // StringBuilder 不是线程安全的,可能会引发异常或数据不一致  
                stringBuilder.append(i);  
            }  
        };  
  
        Thread thread1 = new Thread(task);  
        Thread thread2 = new Thread(task);  
  
        thread1.start();  
        thread2.start();  
  
        try {  
            thread1.join();  
            thread2.join();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
  
        System.out.println("StringBuffer after threading: " + stringBuffer.toString());  
        System.out.println("StringBuilder after threading: " + stringBuilder.toString());  
    }  
}

线程安全性

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

性能

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

对于三者使用的总结:

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

注意事项

  • 性能:对于大量字符串操作,StringBuilder通常比StringStringBuffer性能更好,因为它没有同步开销。但在多线程环境中,StringBuffer的线程安全性可能是必要的。
  • 不可变性String的不可变性使得它在很多情况下更安全,例如作为HashMap的键时,不会因为内容变化而导致哈希值变化。

String为什么是不可变的?

String 在 Java 中被设计为不可变(immutable)的,这主要是基于以下几个原因:

安全性

  • 不可变性提供了线程安全性。由于 String 对象是不可变的,它们可以被多个线程安全地共享而不需要额外的同步机制。
  • 字符串常量池(String Pool)的实现也依赖于字符串的不可变性。字符串常量池是 Java 用来存储字符串字面量的内存区域,它允许相同的字符串字面量只被存储一次,从而节省内存。如果 String 是可变的,那么这种优化就不可能实现,因为相同的字符串引用可能会指向不同的内容。

哈希码缓存

  • String 类被用作 Java 集合框架(如 HashMap 和 HashSet)中的键。这些集合依赖于对象的哈希码来快速查找元素。由于 String 是不可变的,它的哈希码可以在创建时被计算并缓存起来,这样在后续操作中就可以避免重复计算哈希码,从而提高性能。

设计简洁性

  • 不可变性简化了 String 类的设计。如果 String 是可变的,那么它的许多方法(如 substring()replace() 等)都需要返回一个新的 String 对象,以保持原始对象的不变性。然而,由于 String 本身就是不可变的,这些方法可以直接在原始对象上操作(实际上是通过创建一个新的 String 对象来实现的,但对外表现为不可变性),而不需要额外的逻辑来处理可变性。

常量池和字符串字面量

  • Java 编译器和运行时环境对字符串字面量进行了优化,将它们存储在字符串常量池中。这种优化只有在字符串是不可变的情况下才是有效的。如果字符串是可变的,那么编译器就无法确定一个字面量在程序运行期间是否会被修改,因此也就无法安全地将其存储在常量池中。

易于理解和使用

  • 不可变性使得 String 对象的行为更加直观和可预测。当你将一个 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 对象

String[] arr = {"he", "llo", "world"};
String s = "";
for (int i = 0; i < arr.length; i++) {
    s += arr[i];
}
System.out.println(s);

StringBuilder 对象是在循环内部被创建的,这意味着每循环一次就会创建一个 StringBuilder 对象。

如果直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。

String[] arr = {"he", "llo", "world"};
StringBuilder s = new StringBuilder();
for (String value : arr) {
    s.append(value);
}
System.out.println(s);

如果你使用 IDEA 的话,IDEA 自带的代码检查机制也会提示你修改代码。

在 JDK 9 中,字符串相加“+”改为用动态方法 makeConcatWithConstants() 来实现,通过提前分配空间从而减少了部分临时对象的创建。然而这种优化主要针对简单的字符串拼接,如: a+b+c 。对于循环中的大量拼接操作,仍然会逐个动态分配内存(类似于两个两个 append 的概念),并不如手动使用 StringBuilder 来进行拼接效率高。

举报

相关推荐

0 条评论