关于StringBuilder,大部分同学只是简单的记住StringBuilder应该用于字符串拼接,不是+,也不是StringBuffer,那么性能是最好的,真的吗?是吗?也有一些同学听过三个似是而非的经验:1、java编译优化+和StringBuilder效果一样;2、StringBuilder不是线程安全的,为了“安全”最好使用StringBuffer;自己拼接这串日志信息,交给slf4j。1.初始长度这么重要,值得说四遍。StringBuilder内部有一个char[],不断的append()就是不断往char[]里填东西的过程。使用newStringBuilder()时,char[]的默认长度是16,那么,如果要追加第17个字符,该怎么办呢?使用System.arraycopy进行乘法和扩展!!!!这样一来,一来是复制数组的代价,二来,原来的char[]白白浪费了,会被GC丢弃。可以想象,一个长度为129个字符的字符串,经过了16次、32次、64次、128次复制和丢弃,总共申请了496个字符,这在高性能场景下几乎是难以承受的。因此,合理设置初始值非常重要。但是,如果我真的不擅长估算怎么办?稍微高估一点更好,只要字符串***大于16,哪怕浪费一点点也比扩大一倍好。2.Liferay的StringBundler类Liferay的StringBundler类提供了另一种设置长度的思路。append()的时候,并不急于往char[]里塞东西,而是先拿一个String[],全部存起来。起来,把***处所有String的长度加起来构造一个合理长度的StringBuilder。3.然而,上一步就出现了char[]的双重浪费,StringBuilder.toString()//createacopy,不要sharetheearrayreturnnewString(value,0,count);String构造函数会用System.arraycopy()复制一个传入的char[]确保安全性和不变性。如果故事就这样结束了,StringBuilder中的char[]还是会白白牺牲。为了不浪费这些char[],一种方法是利用Unsafe等各种黑科技绕过构造函数,直接给String的char[]和count属性赋值,但是很少有人这样做。另一种更可靠的方法是重用StringBuilder。Reuse也解决了之前的长度设置问题,因为即使一开始估计不准,多扩几次就够了。4.重用StringBuilder的做法来自于JDK中的BigDecimal类(可见JDK代码有多重要没问题),SpringSide将代码抽取出来放入StringBuilderHolder中,只有一个函数publicStringBuildergetStringBuilder(){sb.setLength(0);returnsb;}StringBuilder.setLength()函数只是重置了它的计数指针,而char[]会继续被复用,toString()会将当前计数指针作为参数传递给String的构造函数,所以不要担心覆盖新内容size的旧内容也传入了,可见StringBuilder是完全可复用的。为了避免并发冲突,这个Holder一般设置为ThreadLocal。标准写法见BigDecimal或StringBuilderHolder的注释。5.+和StringBuilderStrings="hello"+user.getName();javac编译后这句话的效果,确实等同于使用StringBuilder,只是没有设置长度。Strings=newStringBuilder().append("hello").append(user.getName());但是,如果是这样的话:Strings="hello";//分隔了一些其他语句s=s+user.getName();每条语句都会生成一个新的StringBuilder,这里有两个StringBuilder,表现完全不同。如果在循环体中s+=i;更不可理喻。据R大学介绍,辛勤工作的JVM工程师正处于优化阶段。根据+XX:+OptimizeStringConcat(JDK7u40后默认开启)将相邻的StringBuilder(中间没有控制语句)合成为一个,他们会拼命猜长度。.所以,保险起见,继续使用StringBuilder,自己设置长度。6.StringBuffer和StringBuilderStringBuffer和StringBuilder都继承自AbstractStringBuilder,唯一的区别是StringBuffer的函数有synchronized关键字。那些说StringBuffer“安全”的同学,其实你什么时候见过几个线程轮流追加一个StringBuffer的情况???7.始终将log的字符串拼接交给slf4j??logger.info("Hello{}",user.getName());对于不知道是否输出的日志,在真正需要输出的时候交给slf4j去拼接,确实可以节省成本。但是对于必须要输出的日志,直接使用StringBuilder拼接会更快。因为看slf4j的实现,其实就是不断的indexof("{}"),不断的subString(),然后不断的使用StringBuilder拼凑起来,没有灵丹妙药。附言。slf4j中的StringBuilder除了原来的Message,还保留了50个字符。如果可变参数加起来超过了50个字符,还是要复制扩充……而且StringBuilder也没有被复用。8.总结StringBuilder默认的写法会拼接长度为129的字符串,申请一个总共625个字符的数组。因此,在高性能场景下,始终考虑使用一个ThreadLocal可重用的StringBuilder。而且复用后就不用再玩猜长度的游戏了。
