连接运算符 (+) 与 concat() [重复]

Posted

技术标签:

【中文标题】连接运算符 (+) 与 concat() [重复]【英文标题】:Concatenation operator (+) vs. concat() [duplicate] 【发布时间】:2012-02-04 00:27:09 【问题描述】:

对于字符串连接,我们可以使用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() 比运算符快,那么我们什么时候应该使用串联运算符(+)

【问题讨论】:

阅读:***.com/questions/693597/… 和 ***.com/questions/47605/java-string-concatenation 关于唯一看起来不同的是,当您添加的字符串长度为零时,它只会返回原始字符串,而不是创建新字符串。 + 运算符可能有点贵……如果您正在执行数百或数千个字符串构建操作,请查看 StringBuffer.append()。通常会看到一个方法建立一个 StringBuffer,然后在最后返回或使用 theBuffer.toString()。 @PankajKumar 好吧,你可能想要 StringBuilder 而不是 StringBuffer 【参考方案1】:

concat 方法总是产生一个带有连接结果的新字符串。

加号运算符由创建 StringBuilder 支持,附加您需要的所有 String 值并进一步调用它的 toString()。

因此,如果您需要连接两个值,concat() 将是更好的选择。如果需要连接 100 个值,则应使用加号运算符或显式使用 StringBuilder(例如,在循环中追加的情况下)。

【讨论】:

您后面的评论仅适用于 同一表达式 中的所有字符串连接。所以一个StringBuilder用于x = a + b + c(假设都是Strings),但两个StringBuilders用于x = a + b; x += c; 作为 Artem 答案的附录,this 链接,这似乎是 09 年关于这个特定问题的博客文章。希望该链接将为 Artem 和 TJ 提供的答案和 cmets 提供更多背景信息,尽管我无法验证文章中的断言,因为我承认自己不是 Java 专家... 你不是说explicitly use StringBuilder 谢谢你,我的英语还很差:-) "Concat 方法总是产生一个带有连接结果的新字符串。"不,它没有:if (otherLen == 0) return this;【参考方案2】:

其实两者都是一样的。如果你看到concat(String paramString)的代码,它会返回一个新的字符串对象,并且在(+)运算符中它还会生成一个新的字符串对象。

如果您不想创建新对象,请使用字符串生成器连接两个字符串。

【讨论】:

至于 (+) 大部分都不是真的【参考方案3】:

一般来说,将字符串与+concat() 连接是一种不好的做法。如果要创建字符串,请改用StringBuilder

【讨论】:

这显然是一个错误的答案。在很多情况下,我们应该使用 + 或 concat() 来代替 StringBuilder/StringBuffer。 问题中显示的情况是在循环中串联字符串,在这种情况下,我相信 StringBuilder 是最好的方法,不仅在 Java 中。【参考方案4】:

我相信连接的“风格”会有所作为。

对于 concat(),它在内部创建一个新的 char 数组缓冲区,并根据该 char 数组返回一个新字符串。

对于 + 运算符,编译器实际上将其转换为使用 StringBuffer/StringBuilder。

因此,如果要连接两个字符串,concat() 绝对是更好的选择,因为创建的对象数量只是结果字符串(以及内部使用的 char 缓冲区),而使用+ 运算符会被翻译到:

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

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

但是,如果您要连接,例如连续五个字符串,每个 concat() 将创建一个新的 String 对象。在使用+ 运算符时,编译器会将语句转换为一个具有多个附加操作的 StringBuilder。这绝对是节省了很多不必要的临时对象实例:

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 将复制每个字符两次,如果它永远不必扩展,但如果字符串很大,则可能需要扩展)。【参考方案5】:

您可以始终使用 +,前提是您只使用 >= Java 1.5 并且您在环形。在 Java 1.5 中,它会导致创建 new StringBuilder 并对其进行处理,直到您的字符串完成为止。这是最快的方法。

无论如何 - 如果你在一个循环中(并用+ 连接字符串) - 循环的每次迭代都会创建一个new StringBuilder - 这不是最好的主意。所以这是你应该强制使用StringBuilderStringBuffer(线程安全)类的地方。

通常,此链接清楚地回答了您的问题,并为您提供了完整的知识:

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

【讨论】:

【参考方案6】:

虽然操作符和方法都给出相同的输出,但它们内部的工作方式不同。

concat() 方法只连接 str1 和 str2 并输出一个字符串,对于少量连接更有效。

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

当使用较少数量的字符串进行连接时,您可以使用 concat 方法。但是,如果您使用大量字符串,则 StringBuilder 方法在性能方面会很快。

【讨论】:

构造一个字符串构造器并重复使用它对性能有好处,但是创建一个字符串构造器,使用它连接少于四个字符串,然后放弃它会适得其反。不幸的是,编译器在使用它们连接两个字符串后经常使用并放弃字符串构建器。【参考方案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 毫秒的好处,可以说内存使用量是 1/10 @Naroji 我看不出内存是 1/10 的多少,15ms 是按一个键所需的时间,在开发时间方面确实微不足道。 消耗的内存是 1/10,正如问题中所提到的。连接运算符:耗时 =31 内存消耗 =“2,259,096” 连接方法:耗时 =16 内存消耗 =“299,592” 60 Hz (~16.66 ms) 滴答计数器?【参考方案8】:

其实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[],无需通常的复制,以确保String 不变性的buf 不可见性。

【讨论】:

我想知道为什么 javac 使用StringBuilder,即使在组合少于四个字符串时也是如此?我认为String.valueOf(strA).concat(strB).concat(strC) 会生成更紧凑的代码,并且——对于字符串长度的每种组合——比strA+strB+strC 实际生成的代码更快,除非 JITter 用更有效的东西替换调用StringBuilder 的代码。跨度>

以上是关于连接运算符 (+) 与 concat() [重复]的主要内容,如果未能解决你的问题,请参考以下文章

前端学数据库之函数

SQLSERVER中有没有concat函数

SQL左连接和group_concat返回重复数据

MySQL GROUP_CONCAT 返回重复值。无法使用 DISTINCT

MYSQL CONCAT 停止使用 WHERE 子句处理连接表 [重复]

JPA 连接运算符