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

san*_*lto 6 java architecture string

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

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

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

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

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

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

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

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

Jon*_*eet 9

只是你的"StringBuilders和线程"的评论的话:即使是在多线程程序,这是非常罕见的希望建立在多个线程的字符串.通常,每个线程都有一些数据集并从中创建一个字符串,通常是将多个字符串连接在一起.然后他们会将其转换StringBuilder为字符串,并且可以在线程之间安全地共享该字符串.

我不认为我曾经见过由于StringBuilder线程之间共享而导致的错误.

我个人希望StringBuffer不存在 - 它是在Java的"让我们同步一切"阶段,导致Vector并且Hashtable几乎已经被Java 2中的未同步ArrayListHashMap类废弃了.它只需要花费一点时间来实现非同步的等效StringBuffer到了.

所以基本上:

  • 当您不想执行操作时使用字符串,并且希望确保没有其他操作
  • 使用StringBuilder来执行操作,通常在短期内
  • StringBuffer除非你真的真的需要它,否则应该避免- 就像我说的那样,我记不起曾经有过这样的情况,StringBuffer而不是StringBuilder当两者都可用时.


Ern*_*ill 8

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

String s1 = s2 + s3;
Run Code Online (Sandbox Code Playgroud)

变成类似的东西

String s1 = new StringBuilder(s2).append(s3).toString();
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 在引入StringBuilder之后,他们将编译器改为使用StringBuilder而不是StringBuffer来实现`+`运算符. (2认同)

bes*_*sss 5

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

StringBuilder应该比StringBuffer快,但是任何精明的JVM都可以优化同步。因此,引入它时,它是一个很大的错过(和不小的打击)。

StringBuffer在创建String时不用于复制char [](非共享变量);但是,这是问题的主要根源,包括为小字符串泄漏了巨大的char []。在1.5中,他们决定每次都必须出现char []的副本,并且实际上使StringBuffer无效(在那里进行同步以确保没有线程游戏可以欺骗String)。尽管可以节省内存,但最终可以帮助GC(除了明显减少的占用空间)之外,通常char []是消耗内存的对象的前三名。

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);
    }
Run Code Online (Sandbox Code Playgroud)

因此,如果第二个字符串长于16个字符,它将获得基础char []的另一个副本。太酷了。

这可能是尝试将StringBuilder / Buffer和char []都安装到32位OS上的同一缓存行(在x86上)的副作用……但是我不确定。

至于调试时间等方面的评论。请使用您的判断力,除暗示以外,我个人不记得有任何关于字符串操作的问题。JDO impl的sql生成器的类似绳索的结构。


编辑:下面我说明了Java设计人员没有做些什么来使String操作更快。请注意,该类旨在用于java.lang包,并且只能通过将其添加到bootstrap classpath中来放置。但是,即使不放在那儿(区别是一行代码!),它仍然比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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 一些事情:1. JVM(AFAIK)只能优化局部变量的同步(转义分析),因此当使用非局部变量时,`StringBuilder`仍将比`StringBuffer`更快。2.使用StringBuilder *记录*您不关心同步的事实。如果正确使用,这会使代码更易于理解。3.您的发布听起来好像`StringBuilder`从来没有比连接更快。您的`concat`代码很好,但是它解决了与`StringBuilder`不同的问题。我怀疑在很多情况下,它会在紧密循环中更快。 (2认同)