循环里 s += x 很快,直到你顺手读了一下它

举报
yd_293143657 发表于 2026/11/13 00:27:29 2026/11/13
【摘要】 线上一个日志拼装函数,200 万次循环里 s += x,压测时 CPU 打满。有人把它改成数组 push 再 join,快了十倍,于是团队里流传「JS 字符串拼接必须用数组」——这个结论在 V8 上是错的。 实验片段 abcdefgh,拼 20 万次,结果 160 万字符:写法耗时+= 循环17.1 ms数组 join5.2 ms只差 3 倍,绝对量都很小。真正的差别在后面。 为什么 += ...

线上一个日志拼装函数,200 万次循环里 s += x,压测时 CPU 打满。有人把它改成数组 push 再 join,快了十倍,于是团队里流传「JS 字符串拼接必须用数组」——这个结论在 V8 上是错的。

实验

片段 abcdefgh,拼 20 万次,结果 160 万字符:

写法 耗时
+= 循环 17.1 ms
数组 join 5.2 ms

只差 3 倍,绝对量都很小。真正的差别在后面。

为什么 += 这么快

V8 里 s += piece 不是每次拷贝整个字符串,而是建一个 ConsString:二叉树节点,左边挂旧串,右边挂新片段。20 万次拼接只是建 20 万个节点,单次 O(1),总成本 O(n)。

代价被推迟了,叫「摊平」(flatten)。只有真需要连续字节时才付:indexOf、charCodeAt、slice、正则、比较、写文件。V8 这时才遍历整棵树、按序拷进一块连续内存,并把根节点换成 flat 版本。

摊平一次的账单

拼完 160 万字符,做各种「第一次访问」,同一个串再访问一次作对照:

操作 第一次 第二次
.length 0.07 ms 0.02 ms
indexOf("zzz") 2.11 ms 0.12 ms
charCodeAt(末尾) 1.54 ms 0.00 ms
toUpperCase() 2.09 ms 0.59 ms
slice(0, 16) 1.46 ms 0.00 ms
split(",") 2.13 ms 0.15 ms

length 不用摊平,长度直接记在节点上;其余第一次都要付一遍账。摊平本身是线性的:40 万字符 0.77 ms、160 万 3.92 ms、640 万 7.68 ms,每字符 1~2 ns。

一次性摊平不贵。贵的是反复摊平。

O(n²) 是怎么长出来的

let s = '';
for (let i = 0; i < n; i++) {
  s += 'x';
  sum += s.charCodeAt(s.length - 1); // 每次读都把当前整串摊平
}

第 i 轮 append 之后,根是 (flat, x) 的 ConsString;charCodeAt 要摊平,就把 i 个字符全拷一遍;摊平后根变成 flat。下一轮再 append 又长出一层。总拷贝量 1+2+…+n。

实测:2000 次 1.48 ms、8000 次 7.04 ms、32000 次 83.27 ms、64000 次 314.76 ms。每翻一倍耗时涨 3.3~3.8 倍,不是线性是平方。按这个斜率,100 万次约 80 秒。

内存:内容 1.6 MB,树 6.1 MB

写法 堆驻留
+= 未摊平 6.1 MB
join(已摊平) 1.5 MB

ASCII 字符 V8 用 one-byte 存,160 万字符就是 1.6 MB。join 那 1.5 MB 基本就是内容本身;+= 多出来的 4.6 MB 是 20 万个 ConsString 节点,每个约 23 字节。缓存在内存里一个 100 MB 的日志串,树可能占 300 MB。

结论

  1. 循环里 s += x 没问题,前提是循环内不读它。cons string 让 append 变成 O(1)。
  2. 循环内 s.length 安全;s.charCodeAt、s.indexOf、s[i]、s.match() 不安全,它们逼着摊平。
  3. 需要反复随机读、跑正则、序列化时,先攒成数组再一次 join,让摊平只发生一次。
  4. 排查这类问题:CPU 各核都忙但单请求慢,直接看字符串处理循环体内的读取语句。

数据来自 Node 24.22.1 / Windows,--expose-gc --max-old-space-size=4096,每个变体独立建串,避免互相摊平影响计时。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。