Java中字符串拼接速度

3

我正在快速原型制作一个SQL查询,但没有按照正确的方式进行操作,而是决定使用一堆字符串拼接来完成,一直认为这样会非常慢,但并不重要,因为我只是在测试查询。令我惊讶的是,Java说这段代码只需要0毫秒就能完成?使用+比使用StringBuilder或类似方法不是需要更长时间吗?

long t = System.currentTimeMillis();
String load = "";
for (String s : loadFields)
    load += s + ", ";

String sql = "SELECT ";
sql += load + "sum(relevance) AS 'score' " +
        "FROM ( ";

for (int i = 0; i < searchFields.length; i++) {
    sql += "SELECT ";
    sql += load;
    sql += rels[i] + " AS relevance FROM articles WHERE " +
            searchFields[i];

    sql += " LIKE '%" + terms[0] + "%' ";
    for (int z = 1; z < terms.length; z++)
        sql += "AND " + searchFields[i] + " LIKE '%" + terms[z] + "%' ";

    if (i != searchFields.length - 1) sql += " UNION ALL ";
}

sql += ") results GROUP BY " + load.substring(0, load.length() - 2) + " ";
sql += "ORDER BY score desc, date desc";
System.out.println("Build Time: " + (System.currentTimeMillis() - t) + " ms");

是的,这很丑陋,但重点不在于解释SQL,而在于告诉我为什么速度如此之快。

构建时间:0毫秒

编辑:我运行了10000次测试,每次使用20个术语,大约需要10秒钟,因此大约为1/10毫秒。现在我想起来了,除非我开始使用非常长的字符串,否则这并不需要太多计算。


如果你想要测量类似这样的东西的速度,你需要运行它很多很多次。 - Winston Ewert
4个回答

9
你只进行了29次字符串连接 - 我希望这需要不到一毫秒的时间。
如果你想要测试这段代码与StringBuilder实现之间的差异,你应该迭代它大约10,000次(并进行适当的JVM预热)。
基准测试
我很好奇在这种情况下的确切差异是什么,所以我将你的代码转换为使用.concat()和StringBuilder,并进行了10,000次迭代(2,000次预热),使用了5个字段和20个术语,所有字符串都是随机生成的32个字符。
结果(以毫秒为单位):
   plus: 19656 (0.5/ms)
 concat: 5656  (1.77/ms)
builder: 578   (17.3/ms)

被迭代的数组通常大小在20左右,所以它不止连接一次,而是多次连接。Java是否有某种优化方法我没有看到的? - Rick Button
看起来它并不像我想象的那么需要计算。感谢您的帮助! - Rick Button
+1:在调用10,000次后(即预热后),您应该计算它所需的时间。如果使用StringBuilder并预先构建每次不变的SQL部分,它将更快。 - Peter Lawrey
@Ricky:是的,我没有计算循环次数,但它仍然比毫秒级别可测量的速度快得多。主要问题仍然是您没有使用带绑定变量的PreparedStatement:手动编写的SQL永远不是一个好主意,并且会对整体性能产生比您使用的任何字符串拼接策略更大的影响。 - Dmitri

2
你正在拼接的字符串非常短。尝试使用长度为100,000或更长的字符串(编写一些代码生成大量的字符串),然后重复这个过程数百次。对于短字符串和只有几个拼接的情况,差异太小以至于无法测量。

那似乎大大增加了时间,感谢您的输入,看起来这只是在小字符串大小下并没有太多计算。 - Rick Button
@Ricky Button:没错。更聪明的算法通常只有在输入规模足够大时才会起作用。 - MAK

1

您正在使用毫秒时钟来测量时间。

  • 您可以测量的绝对最小间隔是一毫秒。这通常是硬件时钟的超过一百万个周期。当前一代CPU可以在这段时间内完成大量计算。

  • 毫秒的实际粒度可能是几毫秒。(在当前一代操作系统中不太可能,但我记得时钟滴答声是20毫秒的日子。)

无论哪种方式,测量的时间通常为零并不奇怪。


为了得到有意义的测量结果,您需要在该代码周围放置一个循环并运行它数十万次。然后再放置另一个循环来重复运行测试...直到测量时间稳定下来。
最后,这将是分配密集型的,并且您需要包括摊销的GC时间。(使用StringBuilder的优化版本将通过不那么分配密集来获胜。)

谢谢,这并不像我最初想象的那样需要大量计算。 - Rick Button

1

我理解的是,使用+意味着Java会创建原始字符串的副本,并在每次添加新部分时将其添加到副本中。我认为这会更慢,但无论如何,它肯定需要比StringBuilder占用更多的内存。似乎更有效的方法是拥有一个StringBuilder,并且只有在添加完内容后才使用.toStringString.valueOf()


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