转载:《StringBuilder在高性能场景下的正确用法》 by 江南白衣

关于StringBuilder,一般同学只简单记住了,字符串拼接要用StringBuilder,不要用+,也不要用StringBuffer,然后性能就是最好的了,真的吗吗吗吗?

还有些同学,还听过三句似是而非的经验:

1. Java编译优化后+和StringBuilder的效果一样;

2. StringBuilder不是线程安全的,为了“安全”起见最好还是用StringBuffer;

3. 永远不要自己拼接日志信息的字符串,交给slf4j来。

1. 初始长度好重要,值得说四次。

StringBuilder的内部有一个char[], 不断的append()就是不断的往char[]里填东西的过程。

new StringBuilder() 时char[]的默认长度是16,然后,如果要append第17个字符,怎么办?

用System.arraycopy成倍复制扩容!!!!

这样一来有数组拷贝的成本,二来原来的char[]也白白浪费了要被GC掉。可以想见,一个129字符长度的字符串,经过了16,32,64, 128四次的复制和丢弃,合共申请了496字符的数组,在高性能场景下,这几乎不能忍。

所以,合理设置一个初始值多重要。

但如果我实在估算不好呢?多估一点点好了,只要字符串最后大于16,就算浪费一点点,也比成倍的扩容好。

2. Liferay的StringBundler类

Liferay的StringBundler类提供了另一个长度设置的思路,它在append()的时候,不急着往char[]里塞东西,而是先拿一个String[]把它们都存起来,到了最后才把所有String的length加起来,构造一个合理长度的StringBuilder。

3. 但,还是浪费了一倍的char[]

浪费发生在最后一步,StringBuilder.toString()

//创建拷贝, 不共享数组
return new String(value, 0, count);

String的构造函数会用 System.arraycopy()复制一把传入的char[]来保证安全性不可变性,如果故事就这样结束,StringBuilder里的char[]还是被白白牺牲了。

为了不浪费这些char[],一种方法是用Unsafe之类的各种黑科技,绕过构造函数直接给String的char[]属性赋值,但很少人这样做。

另一个靠谱一些的办法就是重用StringBuilder。而重用,还解决了前面的长度设置问题,因为即使一开始估算不准,多扩容几次之后也够了。

4. 重用StringBuilder

这个做法来源于JDK里的BigDecimal类(没事看看JDK代码多重要),后来发现Netty也同样使用。SpringSide里将代码提取成StringBuilderHolder,里面只有一个函数

public StringBuilder getStringBuilder() {
sb.setLength(0);
return sb;
}

StringBuilder.setLength()函数只重置它的count指针,而char[]则会继续重用,而toString()时会把当前的count指针也作为参数传给String的构造函数,所以不用担心把超过新内容大小的旧内容也传进去了。可见,StringBuilder是完全可以被重用的。

为了避免并发冲突,这个Holder一般设为ThreadLocal,标准写法见BigDecimal或StringBuilderHolder的注释

不过,如果String的长度不大,那从ThreadLocal里取一次值的代价还更大的多,所以也不能把这个ThreadLocalStringBuilder搞出来后,见到StringBuilder就替换。。。

5. + 与 StringBuilder

String s = “hello ” + user.getName();

这一句经过javac编译后的效果,的确等价于使用StringBuilder,但没有设定长度。

 

String s = new StringBuilder().append(“hello”).append(user.getName());

但是,如果像下面这样:

String s = “hello ”;
// 隔了其他一些语句
s = s + user.getName();

每一条语句,都会生成一个新的StringBuilder,这里就有了两个StringBuilder,性能就完全不一样了。如果是在循环体里s+=i; 就更加多得没谱。

据R大说,努力的JVM工程师们在运行优化阶段, 根据+XX:+OptimizeStringConcat(JDK7u40后默认打开),把相邻的(中间没隔着控制语句) StringBuilder合成一个,也会努力的猜长度。

所以,保险起见还是继续自己用StringBuilder并设定长度好了。

6. StringBuffer 与 StringBuilder

StringBuffer与StringBuilder都是继承于AbstractStringBuilder,唯一的区别就是StringBuffer的函数上都有synchronized关键字。

那些说StringBuffer “安全”的同学,其实你几时看过几个线程轮流append一个StringBuffer的情况???

7. 永远把日志的字符串拼接交给slf4j??

logger.info("Hello {}", user.getName());

对于不知道要不要输出的日志,交给slf4j在真的需要输出时才去拼接的确能省节约成本。

但对于一定要输出的日志,直接自己用StringBuilder拼接更快。因为看看slf4j的实现,实际上就是不断的indexof("{}"), 不断的subString(),再不断的用StringBuilder拼起来而已,没有银弹。

PS. slf4j中的StringBuilder在原始Message之外预留了50个字符,如果可变参数加起来长过50字符还是得复制扩容......而且StringBuilder也没有重用。

8. 小结

StringBuilder默认的写法,会为129长度的字符串拼接,合共申请625字符的数组。所以高性能的场景下,永远要考虑用一个ThreadLocal 可重用的StringBuilder。而且重用之后,就不用再玩猜长度的游戏了。当然,如果字符串只有一百几十字节,也不一定要考虑重用,设好初始值就好。

StringBuilder在高性能场景下的正确用法的更多相关文章

  1. 高性能场景下,HashMap的优化使用建议

    1. HashMap 在JDK 7 与 JDK8 下的差别 顺便理一下HashMap.get(Object key)的几个关键步骤,作为后面讨论的基础. 1.1 获取key的HashCode并二次加工 ...

  2. Java 中 StringBuilder 在高性能用法总结

    关于StringBuilder,一般同学只简单记住了,字符串拼接要用StringBuilder,不要用+,也不要用StringBuffer,然后性能就是最好的了,真的吗吗吗吗? 还有些同学,还听过三句 ...

  3. Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%。再往后,每提高0.1%,优化难度成指数级增长了。哪怕是千分之一,也直接影响用户体验,影响每天上万张机票的销售额。 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。线程安全的StringBuilder取代S

    Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%.再往后,每提高0.1%,优化难度成指数级增长了.哪怕是千分之一,也直接影响用户体验,影响每天上万张机 ...

  4. 高并发场景下System.currentTimeMillis()的性能问题的优化 以及SnowFlakeIdWorker高性能ID生成器

    package xxx; import java.sql.Timestamp; import java.util.concurrent.*; import java.util.concurrent.a ...

  5. 转载~kxcfzyk:Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解

    Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解   多线程c语言linuxsemaphore条件变量 (本文的读者定位是了解Pthread常用多线程API和Pthread互斥锁 ...

  6. 亿级流量场景下,大型缓存架构设计实现【1】---redis篇

    *****************开篇介绍**************** -------------------------------------------------------------- ...

  7. 从getApplicationContext和getApplication再次梳理Android的Application正确用法

    原文地址http://blog.csdn.net/ly502541243/article/details/52105466 原文地址http://blog.csdn.net/ly502541243/a ...

  8. CephRGW 在多个RGW负载均衡场景下,RGW 大文件并发分片上传功能验证

    http://docs.ceph.com/docs/master/radosgw/s3/objectops/#initiate-multi-part-upload 根据分片上传的API描述,因为对同一 ...

  9. Spring MVC中Session的正确用法<转>

    Spring MVC是个非常优秀的框架,其优秀之处继承自Spring本身依赖注入(Dependency Injection)的强大的模块化和可配置性,其设计处处透露着易用性.可复用性与易集成性.优良的 ...

随机推荐

  1. python 全栈开发,Day64(视图,触发器,函数,存储过程,事务)

    昨日内容回顾 pymysql:属于python的一个模块 pip3 install pymysql conn = pymysql.connect(...,charset = 'uft8') 创建游标 ...

  2. IO中File类基本使用

    package chapter10; import java.io.File; public class TestFile01 { public static void main(String[] a ...

  3. oracle中主键自增

    oracle中主键自增 下面用一个例子来说明自增主键的创建: 1.建用户数据表 drop table dectuser; create table dectuser( userid integer p ...

  4. 《剑指offer》-孩子们的游戏(圆圈中最后剩下的数)

    每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此.HF作为牛客的资深元老,自然也准备了一些小游戏.其中,有个游戏是这样的:首先,让小朋友们围成一个大圈.然后,他随机指定一个数m ...

  5. PHP PC端微信扫码支付【模式二】详细教程-附带源码(转)

    博主写这破玩意儿的时候花了大概快两天时间才整体的弄懂逻辑,考虑了一下~还是把所有代码都放出来给大家~抱着开源大无私的精神!谁叫我擅长拍黄片呢?同时也感谢我刚入行时候那些无私帮过我的程序员们! 首先还是 ...

  6. 彻底解决:java.sql.SQLException: Incorrect string value: '\xF0\x9F\x92\x94' for column 'name' at row 1

    转载:https://blog.csdn.net/qq_31122833/article/details/83992085

  7. Storm(三)Storm的原理机制

    一.Storm的数据分发策略 1. Shuffle Grouping 随机分组,随机派发stream里面的tuple,保证每个bolt task接收到的tuple数目大致相同. 轮询,平均分配 2. ...

  8. Python divmod方法

    有95条数据 每十条存一页 all_item = 95 pager = 10 result = all_item.__divmod__(pager) print(result) (9{商},5{余数} ...

  9. 类属性判断 hasattr getattr

  10. mac 回车键、空格键失灵(非物理原因)解决方法

    1.关机2.按住shift+control+option+开关机键,大概10秒左右,观察键盘电源灯是否变色,确定是否硬件故障,不变色表正常.3.先按下开关机键后,放开.然后同时按住option+com ...