StringBuilder和StringBuffer的区别

1734

StringBufferStringBuilder的主要区别是什么?在决定使用其中任何一个时是否存在性能问题?

33个回答

1777

251
StringBuilder旨在作为StringBuffer的替代品,仅当不需要同步时才使用它。 - Joel
100
几乎从不需要同步。如果有人想在 StringBuilder 上进行同步,只需将整个代码块用 synchronized(sb){ } 包围即可。 - locka
25
我认为StringBuffer从来都不是一个好选择(除非你有一个需要它的API)http://vanillajava.blogspot.de/2013/04/why-synchronized-stringbuffer-was-never.html - Peter Lawrey
10
我只在控制台输出和不同的日志工具中看到StringBuffer的用途:许多线程可能会冲突输出。因为你不想要2个输出混杂在一起......但通常在StringBuffer级别同步太底层了,你会想要在附加程序级别进行同步,所以锁定答案是最好的选择,应该弃用StringBuffer。这将为新手节省代码审查时间。 - Remi Morin
24
对于那些混淆这两者的人,一个好的记忆方法是BuFFer先出现,它是较旧且同步实现。较新的Builder类使用构建器模式且是异步的。 - Datageek
显示剩余8条评论

761

StringBuilderStringBuffer更快,因为它不是同步的。

下面是一个简单的基准测试:

public class Main {
    public static void main(String[] args) {
        int N = 77777777;
        long t;

        {
            StringBuffer sb = new StringBuffer();
            t = System.currentTimeMillis();
            for (int i = N; i --> 0 ;) {
                sb.append("");
            }
            System.out.println(System.currentTimeMillis() - t);
        }

        {
            StringBuilder sb = new StringBuilder();
            t = System.currentTimeMillis();
            for (int i = N; i > 0 ; i--) {
                sb.append("");
            }
            System.out.println(System.currentTimeMillis() - t);
        }
    }
}

进行测试运行StringBuffer用时2241毫秒,而StringBuilder用时753毫秒


12
我将字符串文字更改为更大的内容:“the quick brown fox”,并得到了更有趣的结果。基本上,它们的速度差不多。我实际上用光了内存,所以我不得不删除几个七。解释:Hotspot 优化掉了同步。你基本上只是测量 Hotspot 完成这个操作所需的时间(以及可能还有一些其他的优化)。 - Jilles van Gurp
12
你需要在测试前先热身。这个测试对StringBuffer不公平。如果它实际上添加一些东西会更好。事实上,我反转了这个测试,并添加了一个随机字符串,得到了相反的结果。这表明,简单的基准测试是不可靠的。相反的结果显示StringBuffer更快。StringBuilder为5164,而StringBuffer为3699。http://hastebin.com/piwicifami.avrasm - mjs
84
这是我第一次在循环中看到 --> 0,花了我一点时间才意识到它的含义。这种写法实际上可以代替通常的 ...; i>0; i-- 语法吗?请注意,我的翻译尽可能保留原文意思,并使其更加易懂,但不包括解释和其他内容。 - Raimund Krämer
26
那个 i --> 在语法上真的很烦人……一开始我以为是箭头,因为有关 ASCII 艺术的注释。 - Sameer Puri
17
其他人得出了不同的结论:http://alblue.bandlem.com/2016/04/jmh-stringbuffer-stringbuilder.html。应该使用JMH进行基准测试,而不是使用简单的“main()”函数。另外,你的基准测试是不公平的,因为没有进行热身。 - Lukas Eder
显示剩余7条评论

267

基本上,StringBuffer 的方法是同步的,而 StringBuilder 则不是。

这些操作“几乎”相同,但在单个线程中使用同步方法会过度。

这就是基本情况。

引用自 StringBuilder API

这个类 [StringBuilder] 提供了与 StringBuffer 兼容的 API,但不保证同步。此类设计为在单个线程使用字符串缓冲区时(通常情况下)可替换为 StringBuffer 的替代品。如果可能的话,建议优先使用此类,因为在大多数实现中它将更快

因此,它的作用是替代它。

VectorArrayList发生了同样的事情。


2
还有HashtableHashMap - shmosel

195

但需要通过示例来明确区别吗?

StringBufferStringBuilder

除非你真的想要在线程之间共享缓冲区,否则简单地使用StringBuilder即可。 StringBuilder是原始同步的 StringBuffer类的无同步(更少开销=更高效)版本。

StringBuffer先于StringBuilder出现。 Sun公司关注所有条件下的正确性,因此他们将其同步以使其在线程安全情况下工作。

StringBuilder稍后出现。 大多数StringBuffer的用途都是单线程并且不必要地支付同步成本。

由于StringBuilder是没有同步功能的可直接替换 StringBuffer,因此在任何示例中不会有区别。

如果你尝试在线程之间共享,请使用StringBuffer,但需要考虑是否需要更高级别的同步,例如,您可以在使用StringBuilder的方法上同步。


16
第一个好答案!!关键是“除非你在线程之间共享缓冲区”。 - AlexWien

88

首先,让我们看看相似之处: StringBuilder和StringBuffer都是可变的。这意味着您可以在同一位置更改它们的内容。

区别: StringBuffer是可变的并且同步。而StringBuilder是可变的,但默认情况下不同步。

synchronized(同步)的含义: 当某物被同步时,多个线程可以随意访问和修改它而不会有任何问题或副作用。 StringBuffer是同步的,因此您可以在多个线程中使用它而不会有任何问题。

何时使用哪一个? StringBuilder:当您需要一个可修改的字符串,并且仅有一个线程正在访问和修改它时。 StringBuffer:当您需要一个可修改的字符串,并且多个线程正在访问和修改它时。

注意:不要不必要地使用StringBuffer,即如果只有一个线程在修改和访问它,则不要使用它,因为它具有大量用于同步的锁定和解锁代码,这将不必要地占用CPU时间。除非需要,否则不要使用锁定。


2
只想提一下,StringBuffer的单个方法调用是线程安全的。但如果您有多行代码,请使用同步代码块来保证线程安全,使用某些锁定/监视器(通常情况下...)。基本上,不要仅仅假设在您的程序中使用线程安全库就立即保证了线程安全! - Kevin Lee

59
在单线程中,由于JVM的优化,StringBuffer 的性能与 StringBuilder 相差不大。但在多线程中,StringBuilder 不能安全使用。
以下是我的测试(不是基准测试,只是一个测试):
public static void main(String[] args) {

    String withString ="";
    long t0 = System.currentTimeMillis();
    for (int i = 0 ; i < 100000; i++){
        withString+="some string";
    }
    System.out.println("strings:" + (System.currentTimeMillis() - t0));

    t0 = System.currentTimeMillis();
    StringBuffer buf = new StringBuffer();
    for (int i = 0 ; i < 100000; i++){
        buf.append("some string");
    }
    System.out.println("Buffers : "+(System.currentTimeMillis() - t0));

    t0 = System.currentTimeMillis();
    StringBuilder building = new StringBuilder();
    for (int i = 0 ; i < 100000; i++){
        building.append("some string");
    }
    System.out.println("Builder : "+(System.currentTimeMillis() - t0));
}

结果:
字符串数量:319740
缓冲区数量:23
构建器数量:7!

因此,构建器比缓冲区更快,并且比字符串拼接要快得多。 现在让我们使用Executor进行多线程处理:

public class StringsPerf {

    public static void main(String[] args) {

        ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
        //With Buffer
        StringBuffer buffer = new StringBuffer();
        for (int i = 0 ; i < 10; i++){
            executorService.execute(new AppendableRunnable(buffer));
        }
        shutdownAndAwaitTermination(executorService);
        System.out.println(" Thread Buffer : "+ AppendableRunnable.time);

        //With Builder
        AppendableRunnable.time = 0;
        executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
        StringBuilder builder = new StringBuilder();
        for (int i = 0 ; i < 10; i++){
            executorService.execute(new AppendableRunnable(builder));
        }
        shutdownAndAwaitTermination(executorService);
        System.out.println(" Thread Builder: "+ AppendableRunnable.time);

    }

   static void shutdownAndAwaitTermination(ExecutorService pool) {
        pool.shutdown(); // code reduced from Official Javadoc for Executors
        try {
            if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
                pool.shutdownNow();
                if (!pool.awaitTermination(60, TimeUnit.SECONDS))
                    System.err.println("Pool did not terminate");
            }
        } catch (Exception e) {}
    }
}

class AppendableRunnable<T extends Appendable> implements Runnable {

    static long time = 0;
    T appendable;
    public AppendableRunnable(T appendable){
        this.appendable = appendable;
    }

    @Override
    public void run(){
        long t0 = System.currentTimeMillis();
        for (int j = 0 ; j < 10000 ; j++){
            try {
                appendable.append("some string");
            } catch (IOException e) {}
        }
        time+=(System.currentTimeMillis() - t0);
    }
}

现在,将100000个字符串追加到StringBuffer中需要157毫秒。虽然这不是相同的测试,但与之前的37毫秒相比,可以安全地假设使用多线程时,StringBuffer的追加速度会变慢。原因是当JIT / hotspot / compiler / something检测到无需检查锁时,它会进行优化。
但是,如果使用StringBuilder,则会出现java.lang.ArrayIndexOutOfBoundsException,因为并发线程尝试在不应该添加的位置添加内容。
结论是,您不必追逐StringBuffers。在使用线程时,请考虑它们正在执行的任务,而不是试图节省几个纳秒。

6
在进行 StringBuilder 测试之前,您忘记执行 "t0 = System.currentTimeMillis();"。因此,显示的 StringBuilder 数字实际上是运行 stringbuffer 和 stringbuilder 测试所需的时间。添加此行代码,您会发现 StringBuilder 快约两倍。 - Gena Batsyan
1
你应该使用JMH进行基准测试。你的基准测试非常不准确。 - Lukas Eder

42

StringBuilder 是在 Java 1.5 引入的,因此无法与早期JVM兼容。

根据Javadocs:

StringBuilder 类提供了一个 API,与 StringBuffer 兼容,但没有同步的保证。该类被设计为在单线程中使用字符串缓冲区的地方作为其替代品(通常情况下是这种情况)。在可能的情况下,建议优先使用此类,因为在大多数实现下它将更快。


13
1.4已经到达了其服务寿命的尽头,因此几乎不值得担心1.5之前的版本。 - Tom Hawtin - tackline
@tomHawtin-tackline 不一定 - 目前我们大多数人每天都在使用的企业产品还停留在1.4之前的版本。此外,BlackBerry Java是基于1.4开发的,而且它仍然非常流行。 - Richard Le Mesurier
CDC和CLDC没有StringBuilder - Jin Kwon

37

相当不错的问题

以下是我注意到的区别:

StringBuffer:-

StringBuffer is  synchronized
StringBuffer is  thread-safe
StringBuffer is  slow (try to write a sample program and execute it, it will take more time than StringBuilder)

StringBuilder:-

 StringBuilder is not synchronized 
 StringBuilder is not thread-safe
 StringBuilder performance is better than StringBuffer.

常见的事情:

两者具有相同的方法和相同的签名。两者都是可变的。


27

StringBuffer

  • 因此同步,因此线程安全
  • 因此线程安全,因此较慢

StringBuilder

  • 在Java 5.0中引入
  • 异步,因此快速高效
  • 如果需要,用户明确需要对其进行同步
  • 您可以将其替换为StringBuffer,无需进行其他更改

注意:只有单个操作是线程安全的,多个操作不是。例如,如果您调用append两次或appendtoString,则不安全。 - Peter Lawrey

25

StringBuffer

StringBuffer是可变的,这意味着可以更改对象的值。通过StringBuffer创建的对象存储在堆中。StringBuffer具有与StringBuilder相同的方法,但StringBuffer中的每个方法都是同步的,也就是说StringBuffer是线程安全的。

由于这个原因,它不允许两个线程同时访问同一个方法。每个方法一次只能被一个线程访问。

但是,线程安全也有缺点,因为线程安全属性会影响StringBuffer的性能。因此,在调用每个类的相同方法时,StringBuilder比StringBuffer更快。

StringBuffer的值可以更改,这意味着它可以被赋予新值。现在,这是一个最常见的面试问题,即上述类之间的区别。可以使用toString()方法将StringBuffer转换为字符串。

StringBuffer demo1 = new StringBuffer(“Hello”) ;
// The above object stored in heap and its value can be changed .

demo1=new StringBuffer(“Bye”);
// Above statement is right as it modifies the value which is allowed in the StringBuffer

StringBuilder

StringBuilder与StringBuffer相同,它将对象存储在堆中并且也可以修改。StringBuffer和StringBuilder之间的主要区别在于StringBuilder也不是线程安全的。 StringBuilder很快,因为它不是线程安全的。

StringBuilder demo2= new StringBuilder(“Hello”);
// The above object too is stored in the heap and its value can be modified

demo2=new StringBuilder(“Bye”);
// Above statement is right as it modifies the value which is allowed in the StringBuilder

enter image description here

资源: String Vs StringBuffer Vs StringBuilder


StringBuilder 是不可变的,而 String 类型是可变的。 - Brinda Rathod
我不认为Strings和StringBuilders是“快速”的,而StringBuffers则是“非常慢”的。请参见上面的答案。 - kepe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接