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

你有没有想过这种变化在Java编程语言中的含义?

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

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

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

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

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

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

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

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

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

StringBuffer在创建String时使用NOT来复制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个字符串,它将获得底层char []的另一个副本。 非常不酷。

这可能是尝试将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 } } 

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

我认为我从未见过因为线程之间共享StringBuilder而导致的错误。

我个人希望StringBuffer不存在 – 它在Java的“让我们同步一切”阶段,导致VectorHashtable几乎被Java 2中的非同步ArrayListHashMap类所淘汰。它只花了一点时间用于未到来的等效StringBuffer到达。

所以基本上:

  • 当您不想执行操作时使用字符串,并且希望确保没有其他操作
  • 使用StringBuilder执行操作,通常在很短的时间内完成
  • 避免使用StringBuffer除非你真的非常需要它 – 而且正如我所说,我记不起曾经有过使用StringBuffer而不是StringBuilder ,两者都可用。

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%的用法类似于上面的内容,它们被创建,用于将几个字符串附加在一起,然后丢弃。

我在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无济于事。 我必须限制运行和元素的数量,以便从“仅字符串”测试中获得任何结果。

决定使用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("\n"); buffer.append(""); for (int i =0; i < elementCount; i++) { buffer.append(""); } buffer.append(""); //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("\n"); buffer.append(""); for (int i =0; i < elementCount; i++) { buffer.append(""); } buffer.append(""); //System.out.println(buffer.toString()); output = buffer.toString(); long end = System.nanoTime(); return end - start; } 

结果:

在OSX机器上:

 StringBuilder构建和输出持续时间=1047.000000μs 

 StringBuffer构建和输出持续时间=1844.000000μs 


在Win7机器上:
 StringBuilder构建和输出持续时间=1869.000000μs 

 StringBuffer构建和输出持续时间=2122.000000μs

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

参考文献:

这里介绍了System.nanoTime()的使用 – > System.nanoTime()完全没用吗? 在这里 – > 如何计算方法在Java中的执行时间? 。

StringBuilder和StringBuffer的源代码 – > http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/lang/java.lang.htm

这里同步的好概述 – > http://www.javaworld.com/javaworld/jw-07-1997/jw-07-hood.html?page=1