String,StringBuffer和StringBuilder


Answers:


378

变异性差异:

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

线程安全差异:

之间的区别StringBuffer,并StringBuilderStringBuffer是线程安全的。因此,当应用程序只需要在单个线程中运行时,最好使用StringBuilderStringBuilder比效率高StringBuffer

情况:

  • 如果您的字符串不打算更改,请使用String类,因为String对象是不可变的。
  • 如果您的字符串可以更改(例如:字符串构造中的大量逻辑和操作),并且只能从单个线程进行访问,则使用a StringBuilder就足够了。
  • 如果您的字符串可以更改,并且可以从多个线程访问,请使用a StringBuffer因为它StringBuffer是同步的,所以您具有线程安全性。

16
另外,由于String将JVM转换为字节码中的StringBuffer,因此使用String进行逻辑运算相当慢,因此不建议使用。从String转换为StringBuffer,然后再次返回String浪费了很多开销。
Pieter van Niekerk,2010年

2
因此,Strings当我们更改值时,将创建另一个对象。旧对象引用是否为空,以便它可能是的垃圾回收,GC甚至是垃圾回收?
Harsh Vardhan)

@PietervanNiekerk逻辑运算是什么意思?
埃莱2015年

我的意思是逻辑运算是基本的String运算,现在我想问一件事,如@Peter所述,在所有情况下还是应该在某些情况下开始在String上使用StringBuffer代替?
JavaDragon 2016年

@bakkal我可以使用Stringwith的所有方法StringBuilder吗?
roottraveller

47
  • 您可以String在不可变结构适用的情况下使用;从StringCPU 获得新的字符序列可能会在CPU时间或内存上带来不可接受的性能损失(获得子字符串对于CPU来说是有效的,因为未复制数据,但这意味着可能会保留大量的数据)。
  • 您可以使用StringBuilder,当你需要创建一个可变的字符序列,通常是连接几个字符序列在一起。
  • 您可以使用StringBuffer你会使用相同的情况下StringBuilder,但是当改变底层字符串必须是同步的(因为有几个线程读取/ modifyind字符串缓冲区)。

在这里查看示例。


2
简洁但不完整,它缺少使用StringBuilder / Buffer的根本原因,即减少或消除常规String串联行为的重新分配和数组副本。

1
“在处理不可变的字符串时使用String”-毫无意义。String的实例是不可变的,因此注释应显示为“当由于不可变而导致的内存使用无关紧要时,请使用String”。可接受的答案很好地涵盖了它的基础。
fooMonster 2011年

28

基础:

String是一个不可变的类,无法更改。 StringBuilder是一个可变类,可以将其追加,替换或删除字符,并最终将其转换String StringBuffer为的原始同步版本StringBuilder

StringBuilder在所有只有单个线程访问对象的情况下,您都应该首选。

细节:

还要注意,StringBuilder/Buffers这并不是魔术,它们只是将Array用作后备对象,并且一旦阵列满就必须重新分配。确保StringBuilder/Buffer最初创建足够大的对象,而不必每次.append()调用它们时都不断调整它们的大小。

调整大小可能会非常退化。基本上,每次需要扩展时,将后备阵列的大小调整为当前大小的2倍。当StringBuilder/Buffer类开始变大时,这可能导致分配大量RAM而不使用它们。

在Java中String x = "A" + "B";使用StringBuilder幕后。因此,对于简单的情况,声明自己的名字没有任何好处。但是,如果要构建String较大的对象(例如小于4k),则声明StringBuilder sb = StringBuilder(4096);比串联或使用仅包含16个字符的默认构造函数要有效得多。如果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);
    }

最佳实践是,StringBuilder/Buffer如果不立即知道String将要多大但可以猜测的话,将其初始化为比您认为需要的大一点的值。一种比您需要的内存稍微多一点的分配将比许多重新分配和复制要好。

另外注意的初始化的StringBuilder/Buffer一个String作为将只分配字符串+ 16个字符,在大多数情况下会刚开始堕落重新分配并复制周期,你正试图避免的大小。以下直接来自Java 6源代码。

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

如果您偶然遇到了一个StringBuilder/Buffer您没有创建且无法控制被调用的构造函数的实例,那么有一种方法可以避免退化的重新分配和复制行为。致电.ensureCapacity()您想要的尺寸,以确保结果String适合您的尺寸。

替代方案:

请注意,如果您要进行大量的 String构建和操作,则有一种名为Ropes的面向性能的替代品。

另一种选择是StringList通过子类创建实现ArrayList<String>,并添加计数器以跟踪.append()列表的每个其他突变操作上的字符数,然后重写.toString()以创建StringBuilder所需大小的a 并遍历列表并构建在输出中,您甚至可以使StringBuilder实例变量和“缓存”结果,.toString()并且仅在发生更改时才重新生成它。

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


1
难道String x = "A" + "B";真的编译成为一个StringBuilder?为什么不将其编译为String x = "AB";,如果在编译时不知道组件,则只能使用StringBuilder。
Matt Greer 2010年

它可能会优化String常量,从上次反编译字节码时我就不记得了,但是我确实知道,如果那里有任何变量,它将肯定使用StringBuilder实现。您可以下载JDK源代码并了解自己。当然,“ A” +“ B”是人为的例子。

我想知道String.format()。我从未真正看到过它在项目中使用。通常是StringBuilder。好吧,通常它实际上是“ A” +“ B” +“ C”,因为人们很懒;)即使在只有两个字符串被串联的情况下,我也总是使用StringBuilder,因为将来可能还会附加更多的字符串。我从未使用过String.format(),主要是因为我不记得它引入了什么JDK-我看到它是JDK1.5,我会使用它来支持其他选项。
jamiebarrow

9

您的意思是串联吗?

实际示例: 您想从其他许多字符串中创建一个新字符串

例如发送一条消息:

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

StringBuilder

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 StringConcatenation

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”的字符串

疗法没有区别,完全一样的代码为两个字符串执行。


2
这是一个非常糟糕的例子。用“ Dear”初始化StringBuilder意味着第一个.append()将导致重新分配和复制。完全否定您试图通过“普通”串联获得的任何效率。一个更好的示例是使用初始大小创建它,以容纳最终String的全部内容。

1
通常StringBuilder,在分配的右侧使用a 进行String连接不是一个好习惯。如您所说,任何好的实现都将在后台使用StringBuilder。此外,您的示例"a" + "b"将被编译为单个文字,"ab"但如果使用StringBuilder它,则会导致对的两个不必要的调用append()
Mark Peters 2010年

@Mark我不是要使用,"a"+"b"而是要说什么是String串联,我将其更改为明确。您没有说的是,为什么这样做不是一个好习惯。这正是(现代的)编译器所做的。@fuzzy,我同意,特别是当您知道最终字符串的大小为(aprox)时。
OscarRyz

1
我认为这不是特别糟糕的做法,但我当然不希望任何建议这样做。它笨拙,难以阅读,而且过于冗长。另外,它还会鼓励像您这样的错误,在这些错误中您要分解两个本来可以编译为一个的文字。只有剖析告诉我这有所作为,我才会按照您的方式进行。
Mark Peters

@Mark明白了。我在“像模板这样的大代码块”中考虑更多,而不是在每个常规字符串文字中考虑更多。但是,是的,我同意,因为如今他们正在做同样的事情,所以这没有任何意义(10年前是拒绝代码修订的原因):)
OscarRyz 2010年

8
-------------------------------------------------- --------------------------------
                  字符串StringBuffer StringBuilder
-------------------------------------------------- --------------------------------                 
仓储区 常量字符串池堆
可修改的 否(不可变)是(可变)是(可变)
线程安全| 是是否
 性能 快速非常慢快速
-------------------------------------------------- --------------------------------

为什么String性能快而StringBuffer性能很慢?
gaurav

1
@gaurav,您可以阅读其源代码,StringBuffer中的所有方法都是这样synchronised,这就是原因
聆听

8

弦乐家族

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

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

  • 如果使用构造器或方法创建了一个字符串,那么这些字符串将会被存储在堆内存SringConstantPool。但是在保存到池中之前,它会intern()使用equals方法调用方法以检查池中具有相同内容的对象可用性。如果字符串复制在池中可用,则返回引用。否则,将String对象添加到池中并返回引用。

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

    String heapSCP = new String("Yash");
    heapSCP.concat(".");
    heapSCP = heapSCP + "M";
    heapSCP = heapSCP + 777;
    
    // For Example: String Source Code 
    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }
  • 字符串文字存储在中StringConstantPool

    String onlyPool = "Yash";

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

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

  • 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 Interface


什么时候使用哪个。

  • 如果您不想每次都更改值,则最好使用String Class。作为泛型的一部分,如果您想对Comparable<T>值进行排序或比较,请使用String Class

    //ClassCastException: java.lang.StringBuffer cannot be cast to java.lang.Comparable
    Set<StringBuffer> set = new TreeSet<StringBuffer>();
    set.add( threadSafe );
    System.out.println("Set : "+ set);
  • 如果您每次都要修改值,那么StringBuilder的使用要比StringBuffer快。如果多个线程正在修改该值,请使用StringBuffer。


4

另外,它StringBuffer是线程安全的,StringBuilder不是。

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


3

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

从JDK 5版本开始,该类已经添加了一个等效的类,该类旨在供单线程使用StringBuilderStringBuilder通常,该类优先于该类使用,因为该类支持所有相同的操作,但它更快,因为它不执行同步。

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


3

就个人而言,我认为现实世界中没有任何用途StringBuffer。我何时想通过操纵字符序列在多个线程之间进行通信?听起来根本没有用,但是也许我还没有看到光:)


3

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

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

原因是StringBuffer线程安全而StringBuilder不是线程安全。 StringBuilder是一个新类StringBuffer Api,它是引入的,JDK5并且如果在单线程环境中工作,则始终建议使用它,因为Faster

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


2

在Java中,String是不可变的。不可变是指一旦创建了字符串,就无法更改其值。 StringBuffer是可变的。创建StringBuffer对象后,我们只需将内容附加到对象的值,而不是创建新的对象。 StringBuilderStringBuffer相似,但是它不是线程安全的。StingBuilder的方法不同步,但是与其他String相比,Stringbuilder运行最快。您可以通过实现它们来了解String,StringBuilder和StringBuffer之间的区别。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.