源码|String拼接操作”+”的优化?

很多讲Java优化的文章都会强调对String拼接的优化。倒不用特意记,本质上在于对不可变类优势和劣势的理解上。

需要关注的是编译器对String拼接做出的优化,在简单场景下的性能能够与StringBuilder相当,复杂场景下仍然有较大的性能问题。网上关于这一问题讲的非常乱;如果我讲的有什么纰漏,也欢迎指正。

JDK版本:oracle java 1.8.0_102

本文用到了反编译工具jad。在查阅网上关于String拼接操作的优化时发现了这个工具,能同时反编译出来源码和字节码,亲测好用,点我下载

String拼接的性能问题

优化之前,每次用”+”拼接,都会生成一个新的String。特别在循环拼接字符串的场景下,性能损失是极其严重的:

  1. 空间浪费:每次拼接的结果都需要创建新的不可变类
  2. 时间浪费:创建的新不可变类需要初始化;产生大量“短命”垃圾,影响 young gc甚至full gc

所谓简单场景

简单场景和复杂场景是我乱起的名字,帮助理解编译器的优化方案。

简单场景可理解为在一句中完成拼接:

int i = 0;
String sentence = “Hello” + “world” + String.valueOf(i) + “\n”;
System.out.println(sentence);

利用jad可看到优化结果:

int i = 0;
String sentence = (new StringBuilder()).append(“Hello”).append(“world”).append(String.valueOf(i)).append(“\n”).toString();
System.out.println(sentence);

是不是很神奇,竟然把String的拼接操作优化成了StringBuilder#append()!

此时,可以认为已经将简单场景的空间性能、时间性能优化到最优(仅针对String拼接操作而言),看起来编译器已经完成了必要的优化。你可以测试一下,简单场景下的性能能够与StringBuilder相当。但是——“但是”以前的都是废话——编译器的优化对于复杂场景的帮助却很有限了。

所谓复杂场景

所谓复杂场景,可理解为“编译器不确定(或很难确定,于是不做分析)要进行多少次字符串拼接后才需要转换回String”。可能表述不准确,理解个大概就好。

我们分析一个最简单的复杂场景:

String sentence = “”;
for (int i = 0; i < 10000000; i++) {
  sentence += “Hello” + “world” + String.valueOf(i) + “\n”;
}
System.out.println(sentence);

理想的优化方案

当然,无论什么场景,程序猿都可以手动优化:

  • 在性能敏感的场景使用StringBuilder完成拼接。
  • 在性能不敏感的场景使用更方便的String。

PS:别吐槽,这样的API设计是合理的,在合适的地方做合适的事

理想目标是把这件事交给javac和JIT:

  • 设定一个拼接次数的阈值,超过阈值就启动优化(对于javac有一个编译期的阈值,JIT有一个运行期的阈值,以分阶段优化)。
  • 优化时,在拼接前生成StringBuilder对象,将拼接操作换成StringBuilder#append(),继续使用该对象,直至“需要”String对象时,使用StringBuilder#toString()“懒加载”新的String对象。

该优化方案的难度在于代码分析:机器很难知道到底何时“需要”String对象,所以也很难在合适的位置注入代码完成“懒加载”。

虽然很难实现,但还是给出理想的优化结果,以供实际方案对比:

String sentence = “”;
StringBuilder sentenceSB = new StringBuilder(sentence);
for (int i = 0; i < 10000000; i++) {
  sentenceSB.append(“Hello”).append(“world”).append(String.valueOf(i)).append(“\n”);
}
sentence = sentenceSB.toString();
System.out.println(sentence);

实际的优化方案

利用jad查看实际的优化结果:

String sentence = “”;
for (int i = 0; i < 10000000; i++) {
  sentence = (new StringBuilder()).append(sentence).append(“Hello”).append(“world”).append(String.valueOf(i)).append(“\n”).toString();
}
System.out.println(sentence);

可以看到,实际上编译器的优化只能达到简单场景的最优:仅优化字符串拼接的一句。这种优化程度,对于上述复杂场景的性能提升很有限,循环时还是会生成大量短命垃圾,特别是字符串拼接到很大的时候,空间和时间上都是致命的。

通过对理想方案的分析,我们也能理解编译器优化的无奈之处:编译器无法(或很难)通过代码分析判断何时是最晚进行懒加载的时机。为什么呢?我们将代码换个形式可能更容易理解:

String sentence = “”;
for (int i = 0; i < 10000000; i++) {
  sentence = sentence + “Hello” + “world” + String.valueOf(i) + “\n”;
}
System.out.println(sentence);

观察第3行的代码,等式右侧引用了sentence。我肉眼知道这句话只完成了字符串拼接,机器呢?最起码,现在的机器还很难通过代码判断。

待以后将人工智能与编译优化结合起来,就算只能以90%的概率完成优化,也是非常cool的。

总结

这个问题我没有做性能测试。其实也没必要过于深究,与其让编译器以隐晦的方式完成优化,不如用代码进行主动、清晰的优化,让代码能够“自解释”。

那么,如果需要优化,使用StringBuilder吧。


本文链接:源码|String拼接操作”+”的优化?
作者:猴子007
出处:https://monkeysayhi.github.io
本文基于 知识共享署名-相同方式共享 4.0 国际许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名及链接。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,612评论 18 399
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,005评论 25 707
  • 大雪纷飞,君行千里。 望断天涯,相思成疾。 辗转反侧,忧心难眠。 孤身只影,锦衾更寒。 明月清辉,披衣对窗。 问君...
    柳若素阅读 276评论 0 0
  • 说起《战狼2》,《战狼2》开播到今天为止已经整整一个礼拜了,舆论还在不断上升,《战狼2》堪比美国大片,目前也在中国...
    隨風飄蕩阅读 312评论 0 1