连接运算符(+)与concat()函数的区别

28

对于字符串连接,我们可以使用concat()或连接运算符(+)

我进行了以下性能测试,并发现concat()是一种更快速和内存效率更高的字符串连接方法。

100,000次字符串连接比较

String str = null;

//------------Using Concatenation operator-------------
long time1 = System.currentTimeMillis();
long freeMemory1 = Runtime.getRuntime().freeMemory();

for(int i=0; i<100000; i++){
    str = "Hi";
    str = str+" Bye";
}
long time2 = System.currentTimeMillis();
long freeMemory2 = Runtime.getRuntime().freeMemory();

long timetaken1 = time2-time1;
long memoryTaken1 = freeMemory1 - freeMemory2;
System.out.println("Concat operator  :" + "Time taken =" + timetaken1 +
                   " Memory Consumed =" + memoryTaken1);

//------------Using Concat method-------------
long time3 = System.currentTimeMillis();
long freeMemory3 = Runtime.getRuntime().freeMemory();
for(int j=0; j<100000; j++){
    str = "Hi";
    str = str.concat(" Bye");
}
long time4 = System.currentTimeMillis();
long freeMemory4 = Runtime.getRuntime().freeMemory();
long timetaken2 = time4-time3;
long memoryTaken2 = freeMemory3 - freeMemory4;
System.out.println("Concat method  :" + "Time taken =" + timetaken2 +
                   " Memory Consumed =" + memoryTaken2);

结果

Concat operator: Time taken = 31; Memory Consumed = 2259096
Concat method  : Time taken = 16; Memory Consumed = 299592

如果concat()比运算符更快,那么何时应该使用拼接运算符(+)


1
请阅读以下链接:https://dev59.com/BHRB5IYBdhLWcg3wLk1M 和 https://dev59.com/JnVD5IYBdhLWcg3wOo9h - Pankaj Kumar
唯一看起来不同的是,当您添加的字符串长度为零时,它只会将原始字符串返回而不是创建一个新的字符串。如果您正在执行数百或数千个字符串构建操作,则+运算符可能有点昂贵...请查看StringBuffer.append()。通常会看到一个方法构建一个StringBuffer,然后在最后返回或使用theBuffer.toString()。 - Pankaj Kumar
@PankajKumar 嗯,你可能想要使用 StringBuilder 而不是 StringBuffer。 - ymajoros
8个回答

42

concat方法总是生成一个新的字符串,其中包含连接的结果。

加号运算符由StringBuilder创建支持,它会将您需要的所有字符串值附加在一起,然后对其进行toString()调用。

因此,如果您需要连接两个值,使用concat()方法会更好。如果您需要连接100个值,则应该使用加号运算符或明确地使用StringBuilder(例如,在循环中追加时)。


4
你后面的评论只对在 同一表达式 中的所有字符串连接才有效。因此,如果所有变量都是 String 类型,那么 x = a + b + c 只需要使用一个 StringBuilder,但如果分为两步 x = a + b; x += c;,则需要使用两个 StringBuilder - T.J. Crowder
1
作为 Artem 答案的补充,这个链接似乎是一个关于这个特定问题的博客帖子 this ,发布于 09 年。希望这个链接能够为 Artem 和 TJ 提供的答案和评论提供更多背景信息,尽管我不能验证文章中所做出的断言,因为我自己也不是 Java 的专家... - blahman
2
你是不是指“明确地使用 StringBuilder”? - RokL
谢谢,我的英语远非出色 :-) - Artem
“Concat”方法总是生成一个新的字符串,其中包含连接的结果。”不,它并不是这样的:“如果(otherLen == 0){return this;}” - Thilo
concat()方法并不总是产生新的字符串。请参阅Java文档中的concat()方法。如果长度参数字符串为0,则它将返回与Thilo上面评论中所示相同的字符串。 - a Learner

13

实际上,s1 + s2s1.concat(s2) 是非常不同的。

s1 + s2 会被 javac 转换成

(new StringBuilder(String.valueOf(s1)).append(s2).toString();

如果您反编译.class文件,就可以看到它。这种构造方式不太高效,它包括多达三个new char[]分配和三个char[]复制操作。

s1.concat(s2)总是一个new char[]+一个复制操作,详见String.java

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    char buf[] = new char[count + otherLen];
    getChars(0, count, buf, 0);
    str.getChars(0, otherLen, buf, count);
    return new String(0, count + otherLen, buf);
}

请注意,new String(int, int, char[])String 的包私有构造函数。它直接使用 char buf[],而不进行通常的复制以确保 buf 不可见,从而实现了 String 的不变性。


2
我想知道为什么即使组合少于四个字符串,javac也使用StringBuilder? 我认为String.valueOf(strA).concat(strB).concat(strC)会生成更紧凑的代码,并且对于每种字符串长度的组合,都比strA + strB + strC实际生成的更快,除非JITter将调用StringBuilder的代码替换为更有效的内容。 - supercat

7
您的测试需要每个循环在单独的方法中运行至少2秒才有意义。短暂的测试可能很难重现和比较。根据您的时间,似乎您正在使用Windows(即因为您的时间是16和31毫秒;)请尝试使用System.nanoTime()。当您的循环迭代10,000次时,整个方法都被编译。这意味着当启动后面的方法时,它已经被编译了。
回答您的问题,当添加两个字符串时,concat稍微快一些。但是,它带来了输入和概念负担,这可能比您节省的CPU要大得多。即使基于您的测试重复100,000次,也只能节省不到15毫秒,而且它花费了您更多的时间(这可能价值更高)。在JVM的将来版本中,差异始终是优化的,并且您的代码复杂度仍然存在。
编辑:我没有注意到内存结果是可疑的。
String str = null;

//------------Using Concatenation operator-------------
long time1 = System.currentTimeMillis();
long freeMemory1 = Runtime.getRuntime().freeMemory();
for (int i = 0; i < 10000; i++) {
    str = "Hi";
    str = str + " Bye";
}
long time2 = System.currentTimeMillis();
long freeMemory2 = Runtime.getRuntime().freeMemory();

long timetaken1 = time2 - time1;
long memoryTaken1 = freeMemory1 - freeMemory2;
System.out.println("Concat operator  :" + "Time taken =" + timetaken1 + " Memory Consumed= " + memoryTaken1);

str = null;
//------------Using Concat method-------------
long time3 = System.currentTimeMillis();
long freeMemory3 = Runtime.getRuntime().freeMemory();
for (int j = 0; j < 10000; j++) {
    str = "Hi";
    str = str.concat(" Bye");

}
long time4 = System.currentTimeMillis();
long freeMemory4 = Runtime.getRuntime().freeMemory();

long timetaken2 = time4 - time3;
long memoryTaken2 = freeMemory3 - freeMemory4;
System.out.println("Concat method  :" + "Time taken =" + timetaken2 + " Memory Consumed= " + memoryTaken2);

在使用-XX:-UseTLAB -mx1g运行时打印

Concat operator  :Time taken =12 Memory Consumed= 1291456
Concat method  :Time taken =7 Memory Consumed= 560000

内存使用比例约为2:1。在原问题中,每次运行结果都会有所变化,有时.concat()似乎使用更多。


“但是,如果你忽略了 15 毫秒的好处,那么在时间和概念开销方面,它会花费你比这更多的代价(而你的时间很可能更有价值)。但是,如果我们忽略 15 毫秒的好处,那么可以说内存使用量只有原来的十分之一。” - lowLatency
@Naroji 我看不出来内存是十分之一,15毫秒只是按一个键所需的时间,在开发时间方面微不足道。 - Peter Lawrey
内存消耗是问题中提到的十分之一。连接运算符:所需时间=31,内存消耗="2,259,096"。连接方法:所需时间=16,内存消耗="299,592"。 - lowLatency
60 Hz(~16.66毫秒)的滴答计数器? - Peter Mortensen

3
我相信“连接”的方式会有所不同。
对于concat()函数,它内部创建一个新的字符数组缓冲区,并基于该字符数组返回一个新的字符串。
对于+操作符,编译器实际上将其转换为使用StringBuffer/StringBuilder。
因此,如果您要连接两个字符串,使用concat()函数肯定是更好的选择,因为创建的对象数量仅为结果字符串(以及内部使用的字符缓冲区),而使用+操作符将被转换为:
result = strA + strB;
-- translate to -->
result = new StringBuilder(strA).append(strB).toString();

创建了一个额外的StringBuilder实例。

然而,如果你要连接(concatenate)五个字符串,每次调用concat()都会创建一个新的String对象。而使用+运算符,编译器会将该语句转换为一个StringBuilder,并执行多次append操作。这绝对可以节省很多不必要的临时对象:

result = strA + strB + strC + strD + strE;
-- translate to -->
result = new StringBuilder(strA).append(strB).append(strC).append(strD).append(strE).toString();

盈亏平衡点大约是四个字符串;(String.valueOf(strA).concat(strB)).concat(String.valueOf(strC).concat(strD))相对于使用字符串生成器的效率大致持平。在八个字符串以内,连接可能会更慢,但不会非常明显,并且很可能在需要时更快(它将每个字符完全复制三次;如果StringBuilder永远不必扩展,则将复制每个字符两次,但如果字符串很大,则可能需要扩展)。 - supercat

0

如果你使用的是Java 1.5及以上版本并且在循环中没有声明要连接的基本字符串(即要连接的字符串),那么你就可以始终使用+。在Java 1.5中,这将创建一个新的StringBuilder对象,并在该对象上操作直至拼接完成。这是最快的方法。

但是,如果你在循环中使用+来连接字符串,则每次迭代都会创建一个新的StringBuilder对象,这并不是一个好主意。因此,在这种情况下,你应该强制使用StringBuilderStringBuffer(线程安全)类。

总的来说,这个链接清楚地回答了你的问题,并为你提供了完整的知识:

http://littletutorials.com/2008/07/16/stringbuffer-vs-stringbuilder-performance-comparison/


0

虽然运算符和方法都能给出相同的输出结果,但它们内部的工作方式是不同的。

concat() 方法只是将 str1 与 str2 连接起来并输出一个字符串,对于少量的连接操作来说更加高效。

但是使用连接运算符 '+' 时,str1+=str2 将被解释为 str1 = new StringBuilder().append(str1).append(str2).toString();

如果你需要连接大量的字符串,那么使用 StringBuilder 方法在性能方面会更快。而如果只需要连接少量的字符串,则可以使用 concat 方法。


构建一个字符串生成器并重复使用它对性能有好处,但是创建一个字符串生成器,使用它来连接少于四个字符串,然后放弃它是适得其反的。不幸的是,编译器经常在使用字符串生成器连接两个字符串后就将其丢弃。 - supercat

-2

实际上,两者是相同的。如果您查看concat(String paramString)的代码,它将返回一个新的字符串对象,在(+)运算符中它也会生成一个新的字符串对象。

如果您不想创建新的对象,则可以使用字符串构建器来连接两个字符串。


关于 (+),这通常是不正确的。 - dantuch

-2
通常使用 +concat() 连接字符串是一种不好的做法。如果你想创建一个字符串,请使用 StringBuilder

这显然是一个错误的答案。有很多情况下,我们应该使用+或concat()而不是StringBuilder/StringBuffer。 - Adrian Shum
问题中展示的情况是在循环中连接字符串,对于这种情况,我认为StringBuilder是最好的选择,不仅在Java中如此。 - Felype

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接