姐夫说
看到群里有人贴这个。Java编译器真的会把普通的字符串拼接操作“优化”为StringBuilder?有这么糟蹋性能的做法吗?
嗯... 小萌新表示 还好吧. 否则每次在常量空间中创建一个中间值 这样子多不值当. (虽然的在新的JVM标准中 已经把String 从常量空间挪到新生区域了
这是个严重影响性能的做法不是吗?明明目标字符串长度也可以知道,你搞个SB需要额外字符数组翻几次倍最后再复制一份,怪不得总是要和GC作斗争,这么个基础操作都这么浪费。
原来Java出了二十几年,.NET出了十五年了,到现在还有那么多人连如何高效地拼接字符串,什么时候该使用StringBuilder什么时候不该使用都不清楚。还有人说一万个字符串拼接用SB就快了等等,当然不是。SB的使用场景是目标字符串长度不确定的情况,和到底是十个还是十万个字符串,到底最终长度是一百还是一百万都没有关系。比如之前的场景,每个字符串都是确定的,最终长度自然也是确定的,这根本就不应该动用SB。假如你不知道应该怎么做,那只能让你去看下十五年前开始.NET就采取的做法。在目标字符串长度确定的情况下,出现目标字符串外任意一个额外的内存分配都是不及格。
另外有人说“一个”StringBuilder对象,好像没多少开销一样。但是SB不是一个对象,而是一串对象啊。你append过程中随时就会分配一个长度翻倍的新的char数组,然后还要复制一遍。多少人以为用SB就够了一样,都不知道需要指定一个capacity。
比如那段所谓被Java编译器“优化”的代码,capacity也没指定,翻倍和复制几乎肯定发生。当然假如他们知道指定capacity,也不会使用SB这么低效的做法了吧。
刚才忘记切换jdk了,java9 已经不是这样了,会使用makeConcatWithConstants 进行拼接
至于非final时用append来搞,从JDK 1.1时代就是这么玩了。当然,还是写javac的人偷懒,先确定长度再生成SB会好一些,但是碰到为null时怎么调用xxx.length(),搞到最后变成这样: 网页链接
正确的字符串拼接方式 网页链接 算出总长,分配目标字符串内存,把输入的字符串复制到正确的位置。出现任意额外的StringBuilder啊char数组分配什么的都是不及格。
我是真不知道直到 Java 9 才有 StringConcatFactory 这种东西的,所以才没能理解为什么 Java 程序员普遍觉得除了 .append 就只有 StringBuilder 一途
至于为什么 .NET 这边不鼓励滥用 StringBuilder,我觉得就算想一下为什么 Java 9 会有 StringConcatFactory 也该明白了。
多说一句,其实StringBuilder在拼接字符串时也不一定是最优的,因为它其实是把每次Append进去的东西复制展开,因此内存占用是和目标字符串长度相关的。有时候,你拿一个字符串数组/List保留输入字符串,最后用自己写的Concat(string[] input, beginIndex, length)拼起来,此时额外的内存占用就是和字符串数量相关,就远小于目标字符串长度了。而这个临时字符串数组甚至都可以复用,最终效果便又是零(额外)分配了。当然,这种方法并不是没有值得讨论的地方。一是实际开发时,复用一个巨型的StringBuilder,每线程一个(ThreadStatic),这可能也够了,开发起来也更方便(效果相对略差)。二是用StringBuilder时,每次Append的字符串可能可以被立即回收,而用上边描述的方法会导致字符串被长时间引用而被升代(假如它们本身就会被其他地方引用着那么自然就没这方面问题了)。总之内存优化时很多时候就是围绕字符串来的,毕竟字符串代表的是一大块连续内存,而且太容易生成新字符串(Split,ToUpper,Substring等等),操作起来要么浪费要么麻烦。更有甚者是,它很容易/已经被滥用了,例如目标函数就是要接受一个字符串时,你除了生成一个新的就没其他办法了。用Span<char>可以解决部分问题,但它一是太新用不上,二是也不能解决所有问题。收起全文
JEP280 为提高性能所做的努力
网友评论