JavaScript中的字符串拼接与字符串缓冲区

24

我正在阅读一本名为《Web开发专业JavaScript》的书,作者提到与使用数组存储字符串并使用join方法创建最终字符串相比,字符串连接是一种昂贵的操作。我很好奇,于是在这里进行了几次测试,看看它可以节省多少时间,以下是我的测试结果 -

http://jsbin.com/ivako

不知何故,火狐浏览器通常会产生相似的时间,但在IE浏览器中,字符串连接要快得多。所以,这个想法现在是否已经过时了(浏览器可能已经改进了)?


我刚在IE 7(jsbin.com/ivako)中尝试了您的示例,似乎与您所说的相反。我得到:使用加号进行连接:92829毫秒 使用StringBuffer进行连接:125毫秒。在同一台机器上的Firefox 3.5中,我得到了使用加号进行连接:110毫秒,使用StringBuffer进行连接:113毫秒。 - Matthew Lock
这很奇怪。我以为由于我在本地运行脚本,所以结果会有偏差,所以我把它放到了jsbin上。但结果还是一样的。我正在使用IE8和Firefox 3.5。我将在虚拟PC上尝试在IE7上运行此脚本。 - DLS
92829毫秒?那是1分钟32秒。你确定吗? - bucabay
@bucabay 是的,没错。年龄是吧! - Matthew Lock
6个回答

4
即使join()比连接更快,也没有关系。我们谈论的是完全可以忽略的微小毫秒数。
我总是更喜欢结构良好、易于阅读的代码,而不是微不足道的性能提升。我认为使用连接看起来更好,更易于阅读。
只是我的个人意见。

6
在许多次循环中发生的一毫秒并不总是可以忽略不计的。这可能会累加到几秒、几天或者几年——这取决于应用程序,我无法相信你不知道这点。你的看法毫无价值。 - DaveWalley
完全同意@DaveWalley的观点...如果你不考虑算法的复杂度(即运行时间/空间),这可能意味着平滑和卡顿响应之间的差异。我会认为你的两分钱实际上是两分债务。 - Daniel Macias
2
我认为Richard Knop的观点是完全正确的。在项目的早期阶段,可读性非常重要。只要你不在一个i>1000的循环中,串联字符串没有任何问题。请不要陷入“过早优化”的陷阱……这是一个众所周知的反模式,在我的经验中会引起最大的困扰。 - Oliver Watkins

3
在我的系统上(Windows 7中的IE 8),在该测试中,StringBuilder的时间从70%到100%不等 - 也就是说,它并不稳定 - 尽管平均值约为正常附加的95%。
虽然现在很容易说“过早优化”(我认为几乎在每种情况下都是如此),但有一些值得考虑的事情:
反复字符串连接的问题在于重复的内存分配和重复的数据复制(高级字符串数据类型可以减少/消除其中大部分,但现在让我们保持简单模型)。从这里我们提出一些问题:
1. 使用哪种内存分配?在天真的情况下,每个str + = x需要分配str.length + x.length新内存。例如,标准C malloc是一个相当差的内存分配器。 JS实现随着多年的变化而发生了变化,包括更好的内存子系统等。当然,这些变化不会止步于此,并且确实触及了现代JS代码的所有方面。因为现在古老的实现在某些任务上可能非常慢,并不一定意味着相同的问题仍然存在或以相同的程度存在。
2. 像上面一样,Array.join的实现非常重要。如果它在构建最终字符串之前没有预先分配内存,则仅节省数据复制成本 - 现在主内存的吞吐量是多少GB / s? 10,000 x 50几乎没有达到极限。智能Array.join操作与较差的内存分配器相比应该表现得更好,因为重新分配的数量减少了。随着分配成本的降低,这种差异预计将被最小化。
3. 基于JS引擎是否为每个唯一字符串文字创建新对象,微基准测试代码可能存在缺陷。(这会使它偏向Array.join方法,但需要在一般情况下考虑)。
4. 基准测试确实是微基准测试 :) 增加增长大小应根据上述任何或所有条件对性能产生影响。通常很容易展示偏爱某种方法或另一种方法的极端情况 - 预期使用案例通常更为重要。
尽管如此,老实说,对于任何形式的“理智”字符串构建,我只会使用正常的字符串连接,直到确定它成为瓶颈(如果有的话)。
我会重新阅读上面的书面声明,并查看作者是否确实意味着其他隐含的考虑因素,例如“针对非常大的字符串”或“疯狂数量的字符串操作”或“在JScript / IE6中”等等。如果没有,那么这样的声明就像“插入排序是O(n * n)”一样有用[实现成本当然取决于数据状态和n的大小]。
免责声明:代码的速度取决于浏览器,操作系统,底层硬件,月球引力和当然,您的计算机对您的感受。

3
原则上,这本书是正确的。将数组连接起来应该比不断地将字符串连接到同一个字符串中要快得多。作为一个简单的不可变字符串算法,它显然更快。
诀窍在于:JavaScript作者很大程度上都是非专业爱好者,在野外写了很多使用串联的代码,而相对较少使用像数组连接这样的方法的“好”代码。结果是,浏览器的作者可以通过迎合和优化串联这种“坏”的、更常见的选项来获得更好的平均网页速度提升。
所以就是这样。新版本的浏览器有一些相当复杂的优化技巧,可以检测到您正在进行大量的串联操作,并对其进行修改,使其在内部更像是一个数组连接,速度几乎相同。

1

我在这个领域有一些经验,因为我的主要产品是一个大型的仅支持IE浏览器的Web应用程序,它需要进行大量的字符串拼接以构建XML文档并发送到服务器。例如,在最坏的情况下,一个页面可能会有5-10个iframe,每个iframe中都有几百个文本框,每个文本框都有5-10个扩展属性。

对于我们的保存功能,我们遍历每个选项卡(iframe)和该选项卡上的每个实体,提取每个实体上的所有扩展属性,并将它们全部放入一个巨大的XML文档中。

在分析和改进我们的保存方法时,我们发现在IE7中使用字符串连接比使用字符串数组方法慢得多。其他值得注意的地方是访问DOM对象的扩展属性非常慢,所以我们将它们全部放入JavaScript数组中。最后,生成JavaScript数组本身最好在服务器上完成,然后将它们作为文字控件写入页面,在页面加载时执行。


我在我的一个应用程序中遇到了类似的问题,今天我决定尝试这种方法——你觉得怎么样——在IE7中的改进明显可见。 - DLS

0

众所周知,不是所有的浏览器都是相同的。因此,不同领域的性能保证会因浏览器而异。

除此之外,我注意到和你一样的结果;然而,在删除不必要的缓冲类后,直接使用数组和一个10000个字符的字符串,结果更加紧密/一致(在FF 3.0.12中):http://jsbin.com/ehalu/

除非你正在进行大量的字符串连接,否则我认为这种类型的优化是微小的优化。你的时间可能更好地用于限制DOM 回流查询(通常使用document.getElementbyById/getElementByTagName),实现AJAX结果的缓存(适用时),并利用事件冒泡(有一个链接,我现在找不到了)。


我尝试了你的脚本,不幸的是它在Firefox 3.5上一直超时。 - DLS
更突出浏览器及其版本之间性能差异的亮点。 - Justin Johnson

-1

好的,关于这个问题,这里有一个相关的模块:

http://www.openjsan.org/doc/s/sh/shogo4405/String/Buffer/0.0.1/lib/String/Buffer.html

这是一种有效的创建字符串缓冲区的方法,通过使用

var buffer = new String.Buffer();
buffer.append("foo", "bar");

这是我所知道的最快的字符串缓冲实现方式。首先,如果您正在实现字符串缓冲,请不要使用 push 方法,因为它是一个内置方法,而且速度很慢,因为 push 方法会遍历整个参数数组,而不仅仅是添加一个元素。

这真的取决于 join 方法的实现方式,有些 join 方法的实现非常慢,而有些则相对较快。


你有任何链接能证明push方法很慢吗? - Prestaul
你需要处理__proto__以及参数数组的长度,这两个事情本不应该是必要的。http://openjsan.org/doc/j/jh/jhuni/StandardLibrary/1.81/lib/Array.html - jhuni

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