鉴于以下2 toString()
实现,首选哪一个:
public String toString(){
return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}
或
public String toString(){
StringBuilder sb = new StringBuilder(100);
return sb.append("{a:").append(a)
.append(", b:").append(b)
.append(", c:").append(c)
.append("}")
.toString();
}
更重要的是,鉴于我们只有3个属性,它可能没什么区别,但是你会在什么时候从+
concat切换到StringBuilder
?
答案 0 :(得分:881)
版本1更可取,因为它更短且the compiler will in fact turn it into version 2 - 没有任何性能差异。
更重要的是,鉴于我们只有3个 它可能没有的属性 差异,但你在什么时候 从concat切换到builder?
在循环中连接的时候 - 通常编译器不能单独替换StringBuilder
。
答案 1 :(得分:227)
关键是你是在一个地方写一个连接还是在一段时间内累积它。
对于您给出的示例,明确使用StringBuilder没有意义。 (查看第一个案例的编译代码。)
但是如果你正在构建一个字符串,例如在循环内部,使用StringBuilder。
澄清一下,假设hugeArray包含数千个字符串,代码如下:
...
String result = "";
for (String s : hugeArray) {
result = result + s;
}
与以下相比,非常浪费时间和记忆力。
...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
sb.append(s);
}
String result = sb.toString();
答案 2 :(得分:69)
我更喜欢:
String.format( "{a: %s, b: %s, c: %s}", a, b, c );
...因为它简短易读。
我会不优化此速度,除非您在重复次数非常高的循环中使用并且已经衡量了性能差异。
我同意,如果你必须输出很多参数,这个表格会让人感到困惑(就像其中一条评论所说)。在这种情况下,我会切换到一个更易读的形式(可能使用apache-commons的ToStringBuilder - 取自matt b)的答案并再次忽略性能。
答案 3 :(得分:67)
在大多数情况下,您不会看到两种方法之间的实际差异,但很容易构建像这样的最坏情况:
public class Main
{
public static void main(String[] args)
{
long now = System.currentTimeMillis();
slow();
System.out.println("slow elapsed " + (System.currentTimeMillis() - now) + " ms");
now = System.currentTimeMillis();
fast();
System.out.println("fast elapsed " + (System.currentTimeMillis() - now) + " ms");
}
private static void fast()
{
StringBuilder s = new StringBuilder();
for(int i=0;i<100000;i++)
s.append("*");
}
private static void slow()
{
String s = "";
for(int i=0;i<100000;i++)
s+="*";
}
}
输出结果为:
slow elapsed 11741 ms
fast elapsed 7 ms
问题是,+ =追加到一个字符串会重建一个新字符串,因此它会花费与字符串长度呈线性关系的东西(两者之和)。
所以 - 问你的问题:
第二种方法会更快,但它的可读性和维护难度更低。 正如我所说,在你的具体情况下,你可能看不出差异。
答案 4 :(得分:27)
我还与我的老板发生冲突,关于是否使用append或+。他们正在使用Append(我仍然无法弄清楚他们每次创建新对象时都会说出来)。 所以我想做一些R&amp; D.虽然我喜欢Michael Borgwardt的解释,但只想表明一个人将来真的需要知道的解释。
/**
*
* @author Perilbrain
*/
public class Appc {
public Appc() {
String x = "no name";
x += "I have Added a name" + "We May need few more names" + Appc.this;
x.concat(x);
// x+=x.toString(); --It creates new StringBuilder object before concatenation so avoid if possible
//System.out.println(x);
}
public void Sb() {
StringBuilder sbb = new StringBuilder("no name");
sbb.append("I have Added a name");
sbb.append("We May need few more names");
sbb.append(Appc.this);
sbb.append(sbb.toString());
// System.out.println(sbb.toString());
}
}
以上类的反汇编出现为
.method public <init>()V //public Appc()
.limit stack 2
.limit locals 2
met001_begin: ; DATA XREF: met001_slot000i
.line 12
aload_0 ; met001_slot000
invokespecial java/lang/Object.<init>()V
.line 13
ldc "no name"
astore_1 ; met001_slot001
.line 14
met001_7: ; DATA XREF: met001_slot001i
new java/lang/StringBuilder //1st object of SB
dup
invokespecial java/lang/StringBuilder.<init>()V
aload_1 ; met001_slot001
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
ldc "I have Added a nameWe May need few more names"
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
aload_0 ; met001_slot000
invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
astore_1 ; met001_slot001
.line 15
aload_1 ; met001_slot001
aload_1 ; met001_slot001
invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
g;
pop
.line 18
return //no more SB created
met001_end: ; DATA XREF: met001_slot000i ...
; ===========================================================================
;met001_slot000 ; DATA XREF: <init>r ...
.var 0 is this LAppc; from met001_begin to met001_end
;met001_slot001 ; DATA XREF: <init>+6w ...
.var 1 is x Ljava/lang/String; from met001_7 to met001_end
.end method
;44-1=44
; ---------------------------------------------------------------------------
; Segment type: Pure code
.method public Sb()V //public void Sb
.limit stack 3
.limit locals 2
met002_begin: ; DATA XREF: met002_slot000i
.line 21
new java/lang/StringBuilder
dup
ldc "no name"
invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
astore_1 ; met002_slot001
.line 22
met002_10: ; DATA XREF: met002_slot001i
aload_1 ; met002_slot001
ldc "I have Added a name"
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
pop
.line 23
aload_1 ; met002_slot001
ldc "We May need few more names"
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
pop
.line 24
aload_1 ; met002_slot001
aload_0 ; met002_slot000
invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
pop
.line 25
aload_1 ; met002_slot001
aload_1 ; met002_slot001
invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
pop
.line 28
return
met002_end: ; DATA XREF: met002_slot000i ...
;met002_slot000 ; DATA XREF: Sb+25r
.var 0 is this LAppc; from met002_begin to met002_end
;met002_slot001 ; DATA XREF: Sb+9w ...
.var 1 is sbb Ljava/lang/StringBuilder; from met002_10 to met002_end
.end method
;96-49=48
; ---------------------------------------------------------------------------
从上面的两个代码可以看出迈克尔是对的。在每种情况下,只创建一个SB对象。
答案 5 :(得分:24)
从Java 1.5开始,简单的一行连接“+”和StringBuilder.append()生成完全相同的字节码。
因此,为了代码可读性,请使用“+”。
2个例外:
答案 6 :(得分:22)
使用最新版本的Java(1.8),反汇编(javap -c
)显示了编译器引入的优化。 +
以及sb.append()
也会生成非常相似的代码。但是,如果我们在for循环中使用+
,那么检查行为是值得的。
在for循环中使用+添加字符串
Java:
public String myCatPlus(String[] vals) {
String result = "";
for (String val : vals) {
result = result + val;
}
return result;
}
ByteCode :( for
循环摘录)
12: iload 5
14: iload 4
16: if_icmpge 51
19: aload_3
20: iload 5
22: aaload
23: astore 6
25: new #3 // class java/lang/StringBuilder
28: dup
29: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
32: aload_2
33: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload 6
38: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore_2
45: iinc 5, 1
48: goto 12
使用stringbuilder.append
添加字符串爪哇:
public String myCatSb(String[] vals) {
StringBuilder sb = new StringBuilder();
for(String val : vals) {
sb.append(val);
}
return sb.toString();
}
ByteCdoe :( for
循环摘录)
17: iload 5
19: iload 4
21: if_icmpge 43
24: aload_3
25: iload 5
27: aaload
28: astore 6
30: aload_2
31: aload 6
33: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: iinc 5, 1
40: goto 17
43: aload_2
虽然有一点明显差异。在第一种情况下,在使用+
的情况下,为每个for循环迭代创建新的StringBuilder
,并通过执行toString()
调用(29到41)来存储生成的结果。因此,您在+
循环中使用for
运算符时生成了您真正不需要的中间字符串。
答案 7 :(得分:9)
在Java 9中,版本1应该更快,因为它被转换为invokedynamic
调用。有关详细信息,请参阅JEP-280:
这个想法是用对java.lang.invoke.StringConcatFactory的简单invokedynamic调用替换整个StringBuilder追加舞蹈,它将接受需要连接的值。
答案 8 :(得分:7)
Apache Commons-Lang有一个ToStringBuilder类,非常容易使用。它既可以处理附加逻辑,也可以格式化你想要的toString外观。
public void toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("a", a);
tsb.append("b", b)
return tsb.toString();
}
将返回看起来像com.blah.YourClass@abc1321f[a=whatever, b=foo]
的输出。
或者使用链接更精简的形式:
public void toString() {
return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}
或者,如果您想使用反射来包含该类的每个字段:
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
如果需要,您还可以自定义ToString的样式。
答案 9 :(得分:7)
出于性能原因,不鼓励使用+=
(String
连接)。原因是:Java String
是不可变的,每次创建新的连接时都会创建一个新的String
(新的public static void main(String[] args)
{
// warming up
for(int i = 0; i < 100; i++)
RandomStringUtils.randomAlphanumeric(1024);
final StringBuilder appender = new StringBuilder();
for(int i = 0; i < 100; i++)
appender.append(RandomStringUtils.randomAlphanumeric(i));
// testing
for(int i = 1; i <= 10000; i*=10)
test(i);
}
public static void test(final int howMany)
{
List<String> samples = new ArrayList<>(howMany);
for(int i = 0; i < howMany; i++)
samples.add(RandomStringUtils.randomAlphabetic(128));
final StringBuilder builder = new StringBuilder();
long start = System.nanoTime();
for(String sample: samples)
builder.append(sample);
builder.toString();
long elapsed = System.nanoTime() - start;
System.out.printf("builder - %d - elapsed: %dus\n", howMany, elapsed / 1000);
String accumulator = "";
start = System.nanoTime();
for(String sample: samples)
accumulator += sample;
elapsed = System.nanoTime() - start;
System.out.printf("concatenation - %d - elapsed: %dus\n", howMany, elapsed / (int) 1e3);
start = System.nanoTime();
String newOne = null;
for(String sample: samples)
newOne = new String(sample);
elapsed = System.nanoTime() - start;
System.out.printf("creation - %d - elapsed: %dus\n\n", howMany, elapsed / 1000);
}
指纹与旧版in the String pool的指纹不同) 。创建新字符串会给GC带来压力并降低程序速度:对象创建很昂贵。
下面的代码应该使它更加实用和清晰。
builder - 1 - elapsed: 132us
concatenation - 1 - elapsed: 4us
creation - 1 - elapsed: 5us
builder - 10 - elapsed: 9us
concatenation - 10 - elapsed: 26us
creation - 10 - elapsed: 5us
builder - 100 - elapsed: 77us
concatenation - 100 - elapsed: 1669us
creation - 100 - elapsed: 43us
builder - 1000 - elapsed: 511us
concatenation - 1000 - elapsed: 111504us
creation - 1000 - elapsed: 282us
builder - 10000 - elapsed: 3364us
concatenation - 10000 - elapsed: 5709793us
creation - 10000 - elapsed: 972us
运行结果报告如下。
+=
不考虑1次连接的结果(JIT尚未完成其工作),即使是10次连接,性能损失也是相关的;对于成千上万的连接,差异是巨大的。
从这个非常快速的实验中学到的经验(使用上面的代码很容易重现):永远不要使用class Tariff
{
int values[3];
public:
friend std::istream& operator>>(std::istream& input, Tariff& t);
};
std::istream& operator>>(std::istream& input, Tariff& t)
{
// Read and ignore the label "Tariff"
std::string name;
std::getline(input, name, ','); // Read until ',' delimiter.
input >> t.value[0];
// Note: the ',' is not a digit, so it causes an error state,
// which must be cleared.
input.clear();
input >> t.value[1];
input.clear();
input >> t.value[2];
input.clear();
}
将字符串连接在一起,即使在需要一些连接的非常基本的情况下(如上所述,创建新的字符串)反正是昂贵的,并对GC施加压力)。
答案 10 :(得分:4)
尽可能使toString方法可读!
在我的书中唯一的例外是,如果你能证明我消耗了大量的资源:)(是的,这意味着分析)
另请注意,Java 5编译器生成的代码比早期Java版本中使用的手写“StringBuffer”方法更快。如果您使用“+”,则此免费增强功能将免费提供。
答案 11 :(得分:3)
目前的编译器是否仍然需要使用StringBuilder似乎存在争议。所以我想我会给你2美分的经验。
我有一个10k记录的JDBC
结果集(是的,我需要在一个批次中完成所有这些记录。)使用+运算符在我的机器上使用Java 1.8
大约需要5分钟。对同一查询使用stringBuilder.append("")
只需不到一秒钟。
所以区别很大。循环StringBuilder
内部要快得多。
答案 12 :(得分:2)
请参阅以下示例:
//java8
static void main(String[] args) {
case1();//str.concat
case2();//+=
case3();//StringBuilder
}
static void case1() {
List<Long> savedTimes = new ArrayList();
long startTimeAll = System.currentTimeMillis();
String str = "";
for (int i = 0; i < MAX_ITERATIONS; i++) {
long startTime = System.currentTimeMillis();
str = str.concat(UUID.randomUUID()+"---");
saveTime(savedTimes, startTime);
}
System.out.println("Created string of length:"+str.length()+" in "+(System.currentTimeMillis()-startTimeAll)+" ms");
}
static void case2() {
List<Long> savedTimes = new ArrayList();
long startTimeAll = System.currentTimeMillis();
String str = "";
for (int i = 0; i < MAX_ITERATIONS; i++) {
long startTime = System.currentTimeMillis();
str+=UUID.randomUUID()+"---";
saveTime(savedTimes, startTime);
}
System.out.println("Created string of length:"+str.length()+" in "+(System.currentTimeMillis()-startTimeAll)+" ms");
}
static void case3() {
List<Long> savedTimes = new ArrayList();
long startTimeAll = System.currentTimeMillis();
StringBuilder str = new StringBuilder("");
for (int i = 0; i < MAX_ITERATIONS; i++) {
long startTime = System.currentTimeMillis();
str.append(UUID.randomUUID()+"---");
saveTime(savedTimes, startTime);
}
System.out.println("Created string of length:"+str.length()+" in "+(System.currentTimeMillis()-startTimeAll)+" ms");
}
static void saveTime(List<Long> executionTimes, long startTime) {
executionTimes.add(System.currentTimeMillis()-startTime);
if(executionTimes.size()%CALC_AVG_EVERY == 0) {
out.println("average time for "+executionTimes.size()+" concatenations: "+
NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(()->0))+
" ms avg");
executionTimes.clear();
}
}
输出:
10000个连接的平均时间:0.096毫秒平均值 10000个连接的平均时间:0.185毫秒平均值 10000个连接的平均时间:0.327毫秒平均值 10000个连接的平均时间:0.501毫秒平均值 10000个连接的平均时间:0.656毫秒平均值 创建长度为1950000的字符串 17745 ms
10000个连接的平均时间:平均0.21毫秒 10000个连接的平均时间:0.652 ms avg
10000个连接的平均时间:1.129毫秒平均值 10000个连接的平均时间:1.727 ms avg
10000个连接的平均时间:2.302 ms avg
创建的字符串长度:1950000 60279毫秒
10000个连接的平均时间:0.002毫秒平均值 10000个连接的平均时间:0.002毫秒平均值 10000个连接的平均时间:0.002毫秒平均值 10000个连接的平均时间:0.002毫秒平均值 10000个连接的平均时间:0.002毫秒平均值 创建长度为1950000的 100毫秒
随着字符串长度的增加,连接时间也会增加。
这就是明确需要StringBuilder
的地方
如您所见,连接:UUID.randomUUID()+"---"
并不会真正影响时间。
P.S。:我不认为When to use StringBuilder in Java确实与此重复。
这个问题谈到toString()
,其中大部分时间都没有执行大字符串的连接。
答案 13 :(得分:2)
使用&#39; +&#39;进行性能明智的字符串连接因为它必须创建一个全新的String副本,因为字符串在java中是不可变的。如果连接非常频繁,这会发挥特殊作用,例如:在循环内部。 以下是我的IDEA在尝试做这样的事情时的建议:
一般规则:
这是围绕此主题的nice Jon Skeet blog。
答案 14 :(得分:1)
我是否可以指出,如果您要迭代一个集合并使用StringBuilder,您可能需要查看Apache Commons Lang和StringUtils.join()(以不同的方式)?
无论性能如何,它都可以节省您必须为百万分之时间创建StringBuilders和for循环。
答案 15 :(得分:1)
我比较了四种不同的方法来比较性能。我完全不知道gc会发生什么,但对我来说重要的是时间。编译器是这里的重要因素。我在window8.1平台下使用了jdk1.8.0_45。
concatWithPlusOperator = 8
concatWithBuilder = 130
concatWithConcat = 127
concatStringFormat = 3737
concatWithBuilder2 = 46
public class StringConcatenationBenchmark {
private static final int MAX_LOOP_COUNT = 1000000;
public static void main(String[] args) {
int loopCount = 0;
long t1 = System.currentTimeMillis();
while (loopCount < MAX_LOOP_COUNT) {
concatWithPlusOperator();
loopCount++;
}
long t2 = System.currentTimeMillis();
System.out.println("concatWithPlusOperator = " + (t2 - t1));
long t3 = System.currentTimeMillis();
loopCount = 0;
while (loopCount < MAX_LOOP_COUNT) {
concatWithBuilder();
loopCount++;
}
long t4 = System.currentTimeMillis();
System.out.println("concatWithBuilder = " + (t4 - t3));
long t5 = System.currentTimeMillis();
loopCount = 0;
while (loopCount < MAX_LOOP_COUNT) {
concatWithConcat();
loopCount++;
}
long t6 = System.currentTimeMillis();
System.out.println("concatWithConcat = " + (t6 - t5));
long t7 = System.currentTimeMillis();
loopCount = 0;
while (loopCount < MAX_LOOP_COUNT) {
concatStringFormat();
loopCount++;
}
long t8 = System.currentTimeMillis();
System.out.println("concatStringFormat = " + (t8 - t7));
long t9 = System.currentTimeMillis();
loopCount = 0;
while (loopCount < MAX_LOOP_COUNT) {
concatWithBuilder2();
loopCount++;
}
long t10 = System.currentTimeMillis();
System.out.println("concatWithBuilder2 = " + (t10 - t9));
}
private static void concatStringFormat() {
String s = String.format("%s %s %s %s ", "String", "String", "String", "String");
}
private static void concatWithConcat() {
String s = "String".concat("String").concat("String").concat("String");
}
private static void concatWithBuilder() {
StringBuilder builder=new StringBuilder("String");
builder.append("String").append("String").append("String");
String s = builder.toString();
}
private static void concatWithBuilder2() {
String s = new StringBuilder("String").append("String").append("String").append("String").toString();
}
private static void concatWithPlusOperator() {
String s = "String" + "String" + "String" + "String";
}
}
答案 16 :(得分:0)
这是我在Java8中检查的内容
使用StringBuilder
usingStringConcatenation 29321 ms
usingStringBuilder 2 ms
如果要对大量字符串使用字符串连接,那真是一场噩梦。
<body>
One string that <Action>should</Action> work is
<Quantifier>approximate number of</Quantifier> other things.
<script>
$(document).ready(function(){
$("Action").hover(function(){
});
$("Quantifier").hover(function(){
});
});
</script>
<body>
答案 17 :(得分:-2)
我认为我们应该采用StringBuilder附加方法。 原因是
String concatenate每次都会创建一个新的字符串对象(As String是不可变对象),因此它将创建3个对象。
使用“字符串”构建器,只会创建一个对象[StringBuilder是可变的],并且会向其附加另一个字符串。
答案 18 :(得分:-4)
对于像我这样的简单字符串,我更喜欢使用
"string".concat("string").concat("string");
按顺序,我会说构造字符串的首选方法是使用StringBuilder,String#concat(),然后是重载的+运算符。 StringBuilder在处理大字符串时性能显着提升,就像使用+运算符一样,性能大幅下降(随着字符串大小的增加,指数大幅减少)。使用.concat()的一个问题是它可以抛出NullPointerExceptions。