String,StringBuffer和StringBuilder之间的性能和简单性权衡

时间:2011-03-26 15:14:01

标签: java architecture string

你有没有想过这种变化对Java编程语言的影响?

String类被设想为一个不可变类(并且这个决定是故意考虑的)。但字符串连接真的很慢,我自己对它进行了基准测试。所以StringBuffer诞生了。非常好的课程,同步和非常快。但有些人对某些同步块的性能成本不满意,并引入了StringBuilder。

但是,当使用String来连接不太多的对象时,类的不变性使其成为实现线程安全的一种非常自然的方式。当我们想要管理多个字符串时,我可以理解StringBuffer的使用。但是,这是我的第一个问题:

  1. 例如,如果您要添加10个或更少的字符串,那么您是否会在执行时间内将简单性换成几毫秒?

    我也对StringBuilder进行了基准测试。它比StringBuffer更有效(仅提高10%)。但是,如果在您的单线程程序中使用StringBuilder,如果您有时想要将设计更改为使用多个线程,会发生什么?你必须改变StringBuilder的每个实例,如果你忘记了一个,你将会产生一些奇怪的效果(考虑到可能出现的竞争条件)。

  2. 在这种情况下,您会在几小时的调试中交换效果吗?

  3. 好的,就是这样。除了简单的问题(StringBuffer比“+”和线程安全更高效,StringBuilder比StringBuffer更快但没有线程安全)我想知道何时使用它们。

    (重要:我知道它们之间的差异;这是与平台架构和一些设计决策相关的问题。)

5 个答案:

答案 0 :(得分:8)

StringBuffer在Java 1.0中;它是对缓慢或不变性的任何反应。它也不比字符串连接更快或更好;实际上,Java编译器编译

String s1 = s2 + s3;

类似

String s1 = new StringBuilder(s2).append(s3).toString();

如果您不相信我,请使用反汇编程序(例如javap -c)自行尝试。

关于“StringBuffer比串联更快”的事情是指重复连接。在这种情况下,显式创建yoir自己的StringBuffer并重复使用它比使编译器创建其中许多更好。

StringBuilder是出于性能原因在Java 5中引入的,正如您所说。它有意义的原因是StringBuffer / Builder实际上永远不会在创建它们的方法之外共享:99%的使用类似于上面的内容,它们被创建,用于将几个字符串附加在一起,然后丢弃。

答案 1 :(得分:8)

只是对你的“StringBuilders和线程”评论的评论:即使在多线程程序中,非常很少想要跨多个线程构建字符串。通常,每个线程都有一些数据集并从中创建一个字符串,通常是通过将多个字符串连接在一起。然后,他们会将StringBuilder转换为字符串,并且可以在线程之间安全地共享该字符串。

由于线程之间共享StringBuilder,我认为我从未看到错误。

我个人希望StringBuffer不存在 - 它位于Java的“让我们同步一切”阶段,导致VectorHashtable几乎已经被非同步化所取代来自Java 2的ArrayListHashMap类。对于未同步的等效StringBuffer来说,只花了一点时间才到达。

基本上是这样的:

  • 当您不想执行操作时使用字符串,并且希望确保没有其他内容
  • 使用StringBuilder进行操作,通常是在短时间内进行操作
  • 避免使用StringBuffer,除非你确实需要它 - 正如我所说,我不记得永远看到我使用StringBuffer代替的情况StringBuilder,两者都可用时。

答案 2 :(得分:4)

现在,StringBuffer和Builder都是无用的(从性能的角度来看)。 我解释原因:

StringBuilder应该比StringBuffer更快,但任何理智的JVM都可以优化同步。因此,当它被引入时,它是一个巨大的错过(和小命中)。

StringBuffer在创建String时使用不复制char [](在非共享变体中);然而,这是一个主要的问题来源,包括为小字符串泄漏巨大的char []。在1.5中他们决定每次都必须发生char []的副本,这实际上使StringBuffer无用(同步是为了确保没有线程游戏可以欺骗String)。这节省了内存,但最终有助于GC(除了明显减少的占用空间),通常char []是消耗内存的对象的前3位。

String.concat过去是并且仍然是连接2个字符串的最快方式(只有2个字符串...或者可能是3个字符串)。请记住,它不会执行char []的额外副本。

回到无用的部分,现在任何第三方代码都可以实现与StringBuilder相同的性能。即使在java1.1中,我曾经有一个类名AsycnStringBuffer,它与StringBuilder现在完全相同,但它仍然分配比StringBuilder更大的char []。 StrinBuffer / StringBuilder都针对小字符串进行了优化,默认情况下你可以看到c-tor

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

因此,如果第二个字符串长于16个字符串,它将获得底层字符的另一个副本 炭[]。非常不酷。

这可能是尝试将StringBuilder / Buffer和char []兼容到32位操作系统上的同一缓存行(在x86上)的副作用......但我不确定。

至于调试时间等的注释。使用你的判断,我个人不记得曾经有任何问题w /字符串操作,除了impl。 JDO impl的SQL生成器的类似于绳索的结构。


编辑: 下面我将说明java设计人员没有做什么来使String操作更快。 请注意,该类适用于java.lang包,只能通过将其添加到引导类路径中来实现。但是,即使没有放在那里(差异是单行代码!),它仍然比StringBuilder更快,令人震惊?这个类会使string1 + string2 + ...比使用StringBuilder好很多,但是......

package java.lang;

public class FastConcat {

    public static String concat(String s1, String s2){
        s1=String.valueOf(s1);//null checks
        s2=String.valueOf(s2);

        return s1.concat(s2);
    }

    public static String concat(String s1, String s2, String s3){
        s1=String.valueOf(s1);//null checks
        s2=String.valueOf(s2);
        s3=String.valueOf(s3);
        int len = s1.length()+s2.length()+s3.length();
        char[] c = new char[len];
        int idx=0;
        idx = copy(s1, c, idx);
        idx = copy(s2, c, idx);
        idx = copy(s3, c, idx);
        return newString(c);
    }
    public static String concat(String s1, String s2, String s3, String s4){
        s1=String.valueOf(s1);//null checks
        s2=String.valueOf(s2);
        s3=String.valueOf(s3);
        s4=String.valueOf(s4);

        int len = s1.length()+s2.length()+s3.length()+s4.length();
        char[] c = new char[len];
        int idx=0;
        idx = copy(s1, c, idx);
        idx = copy(s2, c, idx);
        idx = copy(s3, c, idx);
        idx = copy(s4, c, idx);
        return newString(c);

    }
    private static int copy(String s, char[] c, int idx){
        s.getChars(c, idx);
        return idx+s.length();

    }
    private static String newString(char[] c){
        return new String(0, c.length, c);
        //return String.copyValueOf(c);//if not in java.lang
    }
}

答案 3 :(得分:1)

我在XP机器上尝试过同样的事情。 StringBuilder有点快,但如果你颠倒了运行的顺序,或进行了多次运行你会注意到结果中的“几乎因子2”将变成10%的优势:

StringBuffer build & output duration= 4282,000000 µs
StringBuilder build & output duration= 4226,000000 µs
StringBuffer build & output duration= 4439,000000 µs
StringBuilder build & output duration= 3961,000000 µs
StringBuffer build & output duration= 4801,000000 µs
StringBuilder build & output duration= 4210,000000 µs

对于您的测试,JVM无济于事。我必须限制运行和元素的数量,以便从“仅字符串”测试得到任何结果。

答案 4 :(得分:0)

决定使用XML练习的简单组合将选项置于测试中。对于希望复制结果的用户,在具有16Gb DDR3 RAM的2.7GHz i5上进行测试。

代码:

   private int testcount = 1000; 
   private int elementCount = 50000;

   public void testStringBuilder() {

    long total = 0;
    int counter = 0;
    while (counter++ < testcount) {
        total += doStringBuilder();
    }
    float f = (total/testcount)/1000;
    System.out.printf("StringBuilder build & output duration= %f µs%n%n", f); 
}

private long doStringBuilder(){
    long start = System.nanoTime();
    StringBuilder buffer = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
    buffer.append("<root>");
      for (int i =0; i < elementCount; i++) {
          buffer.append("<data/>");
      }
      buffer.append("</root>");
     //System.out.println(buffer.toString());
      output = buffer.toString();
      long end = System.nanoTime();
     return end - start;
}


public void testStringBuffer(){
    long total = 0;
    int counter = 0;
    while (counter++ < testcount) {
        total += doStringBuffer();
    }
    float f = (total/testcount)/1000;

    System.out.printf("StringBuffer build & output duration= %f µs%n%n", f); 
}

private long doStringBuffer(){
    long start = System.nanoTime();
    StringBuffer buffer = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
    buffer.append("<root>");
      for (int i =0; i < elementCount; i++) {
          buffer.append("<data/>");
      }
      buffer.append("</root>");
     //System.out.println(buffer.toString());
      output = buffer.toString();

      long end = System.nanoTime();
      return end - start;
}

private int testcount = 1000; private int elementCount = 50000; public void testStringBuilder() { long total = 0; int counter = 0; while (counter++ < testcount) { total += doStringBuilder(); } float f = (total/testcount)/1000; System.out.printf("StringBuilder build & output duration= %f µs%n%n", f); } private long doStringBuilder(){ long start = System.nanoTime(); StringBuilder buffer = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); buffer.append("<root>"); for (int i =0; i < elementCount; i++) { buffer.append("<data/>"); } buffer.append("</root>"); //System.out.println(buffer.toString()); output = buffer.toString(); long end = System.nanoTime(); return end - start; } public void testStringBuffer(){ long total = 0; int counter = 0; while (counter++ < testcount) { total += doStringBuffer(); } float f = (total/testcount)/1000; System.out.printf("StringBuffer build & output duration= %f µs%n%n", f); } private long doStringBuffer(){ long start = System.nanoTime(); StringBuffer buffer = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); buffer.append("<root>"); for (int i =0; i < elementCount; i++) { buffer.append("<data/>"); } buffer.append("</root>"); //System.out.println(buffer.toString()); output = buffer.toString(); long end = System.nanoTime(); return end - start; }

结果:

On OSX machine:

StringBuilder build & output duration= 1047.000000 µs 

StringBuffer build & output duration= 1844.000000 µs 


On Win7 machine:
StringBuilder build & output duration= 1869.000000 µs 

StringBuffer build & output duration= 2122.000000 µs

因此,性能增强可能是特定于平台的,具体取决于JVM如何实现同步。

参考文献:

此处已涵盖System.nanoTime()的使用 - &gt; Is System.nanoTime() completely useless?和此处 - &gt; How do I time a method's execution in Java?

StringBuilder&amp;的来源StringBuffer here - &gt; http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/lang/java.lang.htm

此处同步的良好概述 - &gt; http://www.javaworld.com/javaworld/jw-07-1997/jw-07-hood.html?page=1