ChatGPT解决这个技术问题 Extra ChatGPT

String、StringBuffer 和 StringBuilder

请告诉我比较 StringStringBufferStringBuilder 的实时情况?


1
1ac0

可变性差异:

String不可变的,如果您尝试更改它们的值,则会创建另一个对象,而 StringBufferStringBuilder可变的,因此它们可以更改它们的值。

线程安全差异:

StringBufferStringBuilder 之间的区别在于 StringBuffer 是线程安全的。因此,当应用程序只需要在单个线程中运行时,最好使用 StringBuilderStringBuilderStringBuffer 更有效。

情况:

如果您的字符串不会更改,请使用 String 类,因为 String 对象是不可变的。

如果您的字符串可以更改(例如:字符串构造中的大量逻辑和操作)并且只能从单个线程访问,那么使用 StringBuilder 就足够了。

如果您的字符串可以更改,并且可以从多个线程访问,请使用 StringBuffer,因为 StringBuffer 是同步的,因此您具有线程安全性。


此外,使用 String 进行逻辑操作相当慢,根本不建议使用,因为 JVM 将 String 转换为字节码中的 StringBuffer。大量开销被浪费在从 String 转换为 StringBuffer 然后再转换回 String 上。
因此,在 Strings 中,当我们更改值时,会创建另一个对象。旧对象引用是否被无效,以便它可能被 GC 垃圾收集,或者它甚至是垃圾收集?
@PietervanNiekerk逻辑运算是什么意思?
我的意思是逻辑操作是基本的字符串操作,现在我想问一件事,正如@Peter 所说,我们应该在所有情况下开始在字符串上使用 StringBuffer 还是在某些特定情况下?
@bakkal 我可以将 String 的所有方法与 StringBuilder 一起使用吗?
A
Artefacto

当不可变结构适合时使用 String;从字符串中获取新的字符序列可能会带来不可接受的性能损失,无论是在 CPU 时间还是内存方面(获取子字符串是 CPU 效率高的,因为数据不会被复制,但这意味着可能会保留更大量的数据)。

当您需要创建可变字符序列时使用 StringBuilder,通常将多个字符序列连接在一起。

在与使用 StringBuilder 相同的情况下使用 StringBuffer,但是必须同步对基础字符串的更改(因为多个线程正在读取/修改字符串缓冲区)。

请参阅示例 here


简洁但不完整,它错过了使用 StringBuilder/Buffer 的根本原因,即减少或消除常规字符串连接行为的重新分配和数组复制。
“在处理不可变字符串时使用字符串” - 没有意义。 String 的实例是不可变的,所以也许注释应该是“当由于不可变而导致的内存使用无关紧要时使用 String”。接受的答案很好地涵盖了它的基础。
1
12 revs, 2 users 95% user177800

基础知识:

String 是不可变类,不能更改。 StringBuilder 是一个可变类,可以附加、替换或删除字符并最终转换为 String StringBufferStringBuilder 的原始同步版本

在您只有一个线程访问您的对象的所有情况下,您应该更喜欢 StringBuilder

细节:

另请注意,StringBuilder/Buffers 并不神奇,它们只是使用 Array 作为支持对象,并且 Array 必须在它满时重新分配。确保创建足够大的 StringBuilder/Buffer 对象,而不必在每次调用 .append() 时都不断地调整它们的大小。

重新调整大小会变得非常退化。每次需要扩展时,它基本上都会将支持 Array 的大小重新调整为当前大小的 2 倍。当 StringBuilder/Buffer 类开始变大时,这可能会导致分配大量 RAM 而未被使用。

在 Java 中,String x = "A" + "B"; 在幕后使用 StringBuilder。因此,对于简单的情况,声明自己的情况没有任何好处。但是,如果您要构建较大的 String 对象,例如小于 4k,则声明 StringBuilder sb = StringBuilder(4096); 比连接或使用只有 16 个字符的 default constructor 更有效。如果您的 String 将小于 10k,则使用构造函数将其初始化为 10k 以确保安全。但是如果它被初始化为 10k,那么你写的 1 个字符超过 10k,它将被重新分配并复制到一个 20k 的数组中。所以初始化高比低好。

在自动调整大小的情况下,在第 17 个字符处,后备数组被重新分配并复制到 32 个字符,在第 33 个字符处再次发生这种情况,您可以重新分配并将数组复制到 64 个字符中。您可以看到这会如何退化为 很多 重新分配和复制,而这正是您首先要避免使用 StringBuilder/Buffer 的原因。

这是来自 AbstractStringBuilder 的 JDK 6 源代码

   void expandCapacity(int minimumCapacity) {
    int newCapacity = (value.length + 1) * 2;
        if (newCapacity < 0) {
            newCapacity = Integer.MAX_VALUE;
        } else if (minimumCapacity > newCapacity) {
        newCapacity = minimumCapacity;
    }
        value = Arrays.copyOf(value, newCapacity);
    }

如果您不知道 String 有多大,但您可以猜到,最好将 StringBuilder/Buffer 初始化为比您认为需要的大一点。一次分配比您需要的内存稍多的分配将比大量重新分配和复制要好。

还要注意使用 String 初始化 StringBuilder/Buffer,因为这只会分配 String + 16 个字符的大小,在大多数情况下,这只会开始您试图避免的退化重新分配和复制周期。以下内容直接来自 Java 6 源代码。

public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
    }

如果您碰巧最终得到了一个您没有创建并且无法控制被调用的构造函数的 StringBuilder/Buffer 实例,那么有一种方法可以避免退化的重新分配和复制行为。使用您希望确保生成的 String 适合的大小调用 .ensureCapacity()

替代方案:

需要注意的是,如果您要进行真正的繁重 String 构建和操作,还有一个更注重性能的替代方案,称为 Ropes

另一种选择是通过子类 ArrayList<String> 创建一个 StringList 实现,并添加计数器以跟踪每个 .append() 上的字符数和列表的其他突变操作,然后覆盖 .toString() 以创建一个 { 5} 您需要的确切大小并遍历列表并构建输出,您甚至可以将 StringBuilder 设为实例变量并“缓存”.toString() 的结果,并且只需要在发生变化时重新生成它.

在构建固定格式的输出时也不要忘记 String.format(),编译器可以对其进行优化,因为它们会变得更好。


String x = "A" + "B"; 真的编译为 StringBuilder 吗?为什么不直接编译为 String x = "AB";,如果组件在编译时未知,它应该只使用 StringBuilder。
它可能会优化字符串常量,我不记得上次反编译的字节码,但我知道如果那里有任何变量,它肯定会使用 StringBuilder 实现。您可以下载 JDK 源代码并自行查找。 “A”+“B”肯定是一个人为的例子。
我想知道 String.format()。我还没有真正看到它在项目中使用过。通常是 StringBuilder。好吧,通常它实际上是“A”+“B”+“C”,因为人们很懒;)我倾向于总是使用 StringBuilder,即使它只是连接两个字符串,因为将来可能会附加更多的字符串.我从来没有使用过 String.format() 主要是因为我不记得它被引入了什么 JDK - 我看到它是 JDK1.5,我会使用它来支持其他选项。
O
OscarRyz

你的意思是串联?

现实世界的例子:您想从许多其他字符串中创建一个新字符串。

例如发送消息:

细绳

String s = "Dear " + user.name + "<br>" + 
" I saw your profile and got interested in you.<br>" +
" I'm  " + user.age + "yrs. old too"

字符串生成器

String s = new StringBuilder().append.("Dear ").append( user.name ).append( "<br>" ) 
          .append(" I saw your profile and got interested in you.<br>") 
          .append(" I'm  " ).append( user.age ).append( "yrs. old too")
          .toString()

或者

String s = new StringBuilder(100).appe..... etc. ...
// The difference is a size of 100 will be allocated upfront as  fuzzy lollipop points out.

StringBuffer(语法和StringBuilder完全一样,效果不同)

关于

StringBufferStringBuilder

前者是同步的,而后者不是。

因此,如果您在单个线程中多次调用它(这是 90% 的情况),StringBuilder 的运行速度会大大更快,因为它不会停下来查看它是否拥有线程锁.

因此,建议使用 StringBuilder (当然,除非您有多个线程同时访问它,这种情况很少见)

String 连接(使用 + 运算符)可能会被编译器优化为在下面使用 StringBuilder,因此,不再需要担心,在 Java 的早期,这是一些事情每个人都说应该不惜一切代价避免,因为每个连接都创建了一个新的 String 对象。现代编译器不再这样做了,但使用 StringBuilder 仍然是一个好习惯,以防万一您使用“旧”编译器。

编辑

只是为了好奇,这就是编译器为这个类所做的:

class StringConcatenation {
    int x;
    String literal = "Value is" + x;
    String builder = new StringBuilder().append("Value is").append(x).toString();
}

javap -c 字符串连接

Compiled from "StringConcatenation.java"
class StringConcatenation extends java.lang.Object{
int x;

java.lang.String literal;

java.lang.String builder;

StringConcatenation();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   new #2; //class java/lang/StringBuilder
   8:   dup
   9:   invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   12:  ldc #4; //String Value is
   14:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   17:  aload_0
   18:  getfield    #6; //Field x:I
   21:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   24:  invokevirtual   #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   27:  putfield    #9; //Field literal:Ljava/lang/String;
   30:  aload_0
   31:  new #2; //class java/lang/StringBuilder
   34:  dup
   35:  invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   38:  ldc #4; //String Value is
   40:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   43:  aload_0
   44:  getfield    #6; //Field x:I
   47:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   50:  invokevirtual   #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   53:  putfield    #10; //Field builder:Ljava/lang/String;
   56:  return

}

第 5 - 27 行用于名为“literal”的字符串

第 31-53 行用于名为“builder”的字符串

没有区别,对两个字符串执行完全相同的代码。


这是一个非常糟糕的例子。用“ Dear”初始化 StringBuilder 意味着第一个 .append() 将导致重新分配和复制。完全否定您试图通过“正常”连接获得的任何效率。一个更好的例子是创建一个初始大小来保存最终字符串的全部内容。
在赋值的右侧使用 StringBuilder 进行字符串连接通常不是一个好习惯。正如您所说,任何好的实现都会在幕后使用 StringBuilder 。此外,您的 "a" + "b" 示例将被编译为单个文字 "ab",但如果您使用 StringBuilder,则会导致对 append() 的两次不必要的调用。
@Mark 我不是要使用 "a"+"b" 而是要说什么是 String concatenation 关于我将其更改为明确的。你不说的是,为什么这样做不是一个好习惯。这正是(现代)编译器所做的。 @fuzzy,我同意,特别是当您知道最终字符串的大小( aprox )时。
我不认为这是特别糟糕的做法,但我当然不希望有任何建议这样做。它笨重且难以阅读,而且过于冗长。另外,它鼓励像你这样的错误,你正在分解两个本来可以编译为一个的文字。只有当分析告诉我它有所作为时,我才会按照你的方式去做。
@Mark 知道了。我更多地考虑“像模板这样的大块代码”,而不是真正考虑每个常规字符串文字。但是,是的,我同意,因为他们现在做同样的事情,这没有意义(10 年前是拒绝更改代码修订的理由):)
A
Abhijit Maity
----------------------------------------------------------------------------------
                  String                    StringBuffer         StringBuilder
----------------------------------------------------------------------------------                 
Storage Area | Constant String Pool         Heap                   Heap 
Modifiable   |  No (immutable)              Yes( mutable )         Yes( mutable )
Thread Safe  |      Yes                     Yes                     No
 Performance |     Fast                 Very slow                  Fast
----------------------------------------------------------------------------------

为什么 String 性能很快而 StringBuffer 性能很慢?
@gaurav 你可以阅读它的源代码,StringBuffer 中的所有方法都是synchronised,这就是为什么
字符串的存储区域取决于我们如何创建字符串,比如说 - Approach-1: String str1= "ABC" - 该值将存储在 SCP 中。(它在堆内存中) Approach-2: String str1= new String("ABC")- 对象将在堆内存中创建。
Y
Yash

https://i.stack.imgur.com/1S4Mz.png

细绳

String class 代表字符串。 Java 程序中的所有字符串文字,例如 "abc" 都是作为此类的实例实现的。

字符串对象一旦创建就不可更改,我们无法更改。 (字符串是常量)

如果使用构造函数或方法创建字符串,则这些字符串将存储在堆内存和 SringConstantPool 中。但是在保存到池中之前,它会调用 intern() 方法来使用 equals 方法检查池中相同内容的对象可用性。如果字符串副本在池中可用,则返回引用。否则,将 String 对象添加到池中并返回引用。 Java 语言为字符串连接运算符 (+) 以及将其他对象转换为字符串提供了特殊支持。字符串连接是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的。字符串 heapSCP = new String("Yash"); heapSCP.concat(".");堆SCP =堆SCP +“M”;堆SCP =堆SCP + 777; // 例如:字符串源代码 public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { 返回这个; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len);返回新字符串(buf,true); }

Java 语言为字符串连接运算符 (+) 以及将其他对象转换为字符串提供了特殊支持。字符串连接是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的。

字符串文字存储在 StringConstantPool 中。 String onlyPool = "Yash";

StringBuilderStringBuffer 是可变的字符序列。这意味着可以更改这些对象的值。 StringBuffer 具有与 StringBuilder 相同的方法,但 StringBuffer 中的每个方法都是同步的,因此它是线程安全的。

StringBuffer 和 StringBuilder 数据只能使用 new 运算符创建。因此,它们存储在堆内存中。

StringBuilder 的实例对于多线程使用是不安全的。如果需要这种同步,则建议使用 StringBuffer。 StringBuffer threadSafe = new StringBuffer("Yash"); threadSafe.append(".M"); threadSafe.toString(); StringBuilder nonSync = new StringBuilder("Yash"); nonSync.append(".M"); nonSync.toString();

StringBuffer 和 StringBuilder 有一个特殊的方法,例如 replace(int start, int end, String str) 和 reverse()。注意: StringBuffer 和 SringBuilder 是可变的,因为它们提供了 Appendable 接口的实现。

什么时候用哪一个。

如果您不打算每次都更改值,那么最好使用字符串类。作为泛型的一部分,如果您想对 Comparable 进行排序或比较一个值,那么请使用 String 类。 //ClassCastException: java.lang.StringBuffer 无法转换为 java.lang.Comparable Set set = new TreeSet(); set.add(threadSafe); System.out.println("设置:"+ 设置);

如果您要在每次使用比 StringBuffer 更快的 StringBuilder 时修改值。如果多个线程正在修改 StringBuffer 的值。


L
Lars Andren

此外,StringBuffer 是线程安全的,而 StringBuilder 不是。

因此,在不同线程访问它的实时情况下,StringBuilder 可能会产生不确定的结果。


J
Jesper

请注意,如果您使用的是 Java 5 或更高版本,则应使用 StringBuilder 而不是 StringBuffer。从 API 文档:

从 JDK 5 开始,该类已经补充了一个为单线程使用而设计的等效类 StringBuilder。通常应优先使用 StringBuilder 类,因为它支持所有相同的操作,但速度更快,因为它不执行同步。

实际上,您几乎不会同时在多个线程中使用它,因此 StringBuffer 所做的同步几乎总是不必要的开销。


f
fredoverflow

就个人而言,我认为 StringBuffer 没有任何实际用途。我什么时候想通过操作字符序列在多个线程之间进行通信?这听起来一点用都没有,但也许我还没有看到光明:)


H
Hitesh Garg

String 与其他两个类的区别在于 String 是不可变的,而另外两个是可变类。

但是为什么我们有两个用于相同目的的类?

原因是 StringBuffer 是线程安全的,而 StringBuilder 不是。 StringBuilderStringBuffer Api 上的一个新类,它是在 JDK5 中引入的,如果您在单线程环境中工作,总是推荐使用它,因为它很多 Faster

有关完整的详细信息,您可以阅读 http://www.codingeek.com/java/stringbuilder-and-stringbuffer-a-way-to-create-mutable-strings-in-java/


r
rajat ghai

在 java 中,String 是不可变的。不可变意味着一旦创建了 String ,我们就不能改变它的值。 StringBuffer 是可变的。创建 StringBuffer 对象后,我们只需将内容附加到对象的值,而不是创建新对象。 StringBuilder 类似于 StringBuffer 但它不是线程安全的。 StingBuilder 的方法不同步,但与其他字符串相比,Stringbuilder 运行速度最快。您可以通过实施它们来了解 String, StringBuilder and StringBuffer 之间的区别。


B
Borislav Stoilov

这是一个小代码片段,说明了如何在异步环境中破坏 StringBuilder

public static void main(String[] args) throws InterruptedException {
    StringBuilder builder = new StringBuilder();

    ExecutorService executorService = Executors.newFixedThreadPool(50);
    for (int i = 0; i < 1000 * 1000; i++) {
        executorService.submit(new AppendRunnable(builder));
    }

    executorService.shutdown();
    executorService.awaitTermination(1, TimeUnit.MINUTES);

    Stream.of(builder.toString().split(System.lineSeparator()))
        .filter(line -> !line.equals("I just appended this!"))
        .forEach(System.out::println);
}

record AppendRunnable(StringBuilder stringBuilder) implements Runnable {

    @Override
    public void run() {
        stringBuilder.append("I just appended this!\n");
    }
}

本质上,我们将字符串附加到构建器,并且我们希望它们都等于“我刚刚附加了这个!”。但它们不是,其中一些以空字符为前缀,因为构建器的内部字符缓冲区不同步并且它被错误地调整大小。在这种情况下使用 StringBuffer 可以解决问题。更详细的信息here