你有没有想过这种变化对Java编程语言的影响?
String类被设想为一个不可变类(并且这个决定是故意考虑的)。但字符串连接真的很慢,我自己对它进行了基准测试。所以StringBuffer诞生了。非常好的课程,同步和非常快。但有些人对某些同步块的性能成本不满意,并引入了StringBuilder。
但是,当使用String来连接不太多的对象时,类的不变性使其成为实现线程安全的一种非常自然的方式。当我们想要管理多个字符串时,我可以理解StringBuffer的使用。但是,这是我的第一个问题:
例如,如果您要添加10个或更少的字符串,那么您是否会在执行时间内将简单性换成几毫秒?
我也对StringBuilder进行了基准测试。它比StringBuffer更有效(仅提高10%)。但是,如果在您的单线程程序中使用StringBuilder,如果您有时想要将设计更改为使用多个线程,会发生什么?你必须改变StringBuilder的每个实例,如果你忘记了一个,你将会产生一些奇怪的效果(考虑到可能出现的竞争条件)。
在这种情况下,您会在几小时的调试中交换效果吗?
好的,就是这样。除了简单的问题(StringBuffer比“+”和线程安全更高效,StringBuilder比StringBuffer更快但没有线程安全)我想知道何时使用它们。
(重要:我知道它们之间的差异;这是与平台架构和一些设计决策相关的问题。)
答案 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的“让我们同步一切”阶段,导致Vector
和Hashtable
几乎已经被非同步化所取代来自Java 2的ArrayList
和HashMap
类。对于未同步的等效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