StringBuilder在高性能场景下的正确用法
转载:《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在高性能场景下的正确用法的更多相关文章
- 高性能场景下,HashMap的优化使用建议
1. HashMap 在JDK 7 与 JDK8 下的差别 顺便理一下HashMap.get(Object key)的几个关键步骤,作为后面讨论的基础. 1.1 获取key的HashCode并二次加工 ...
- Java 中 StringBuilder 在高性能用法总结
关于StringBuilder,一般同学只简单记住了,字符串拼接要用StringBuilder,不要用+,也不要用StringBuffer,然后性能就是最好的了,真的吗吗吗吗? 还有些同学,还听过三句 ...
- Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%。再往后,每提高0.1%,优化难度成指数级增长了。哪怕是千分之一,也直接影响用户体验,影响每天上万张机票的销售额。 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。线程安全的StringBuilder取代S
Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%.再往后,每提高0.1%,优化难度成指数级增长了.哪怕是千分之一,也直接影响用户体验,影响每天上万张机 ...
- 高并发场景下System.currentTimeMillis()的性能问题的优化 以及SnowFlakeIdWorker高性能ID生成器
package xxx; import java.sql.Timestamp; import java.util.concurrent.*; import java.util.concurrent.a ...
- 转载~kxcfzyk:Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解
Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解 多线程c语言linuxsemaphore条件变量 (本文的读者定位是了解Pthread常用多线程API和Pthread互斥锁 ...
- 亿级流量场景下,大型缓存架构设计实现【1】---redis篇
*****************开篇介绍**************** -------------------------------------------------------------- ...
- 从getApplicationContext和getApplication再次梳理Android的Application正确用法
原文地址http://blog.csdn.net/ly502541243/article/details/52105466 原文地址http://blog.csdn.net/ly502541243/a ...
- CephRGW 在多个RGW负载均衡场景下,RGW 大文件并发分片上传功能验证
http://docs.ceph.com/docs/master/radosgw/s3/objectops/#initiate-multi-part-upload 根据分片上传的API描述,因为对同一 ...
- Spring MVC中Session的正确用法<转>
Spring MVC是个非常优秀的框架,其优秀之处继承自Spring本身依赖注入(Dependency Injection)的强大的模块化和可配置性,其设计处处透露着易用性.可复用性与易集成性.优良的 ...
随机推荐
- windows service程序的Environment.CurrentDirectory路径
当前工作目录Environment.CurrentDirectory,对于winform程序,其是在程序放置的目录里, 而windows service的Environment.CurrentDire ...
- c3p0和QueryRunner的结合使用,让开发更加简便
1:DBUtils中的QueryRunner的使用: 1.1:QueryRunner中提供了对SQL语句操作的api: 1.2:主要有三个方法: 1.2.1:query():用于执行select(查询 ...
- 多线程中实现ApplicationContextAware接口获取需要的bean,applicationContext.getBea未返回也未报错
唉,面试失败了有点难过. https://q.cnblogs.com/q/95168/#a_208239
- [HDU] 5306 Gorgeous Sequence [区间取min&求和&求max]
题解: 线段树维护区间取min求和求max 维护最小值以及个数,次小值 标记清除时,分情况讨论 当lazy>max1 退出 当max1>lazy>max2(注意不要有等号) 更新 否 ...
- 项目部署到liunx环境下访问接口返回异常
1.访问接口返回异常 已经连续踩了两次这个坑了.所以记下来了.方便下次搜索! 项目在window下运行正常,无任何异常! 但是部署到liunx环境下的服务器上就有问题 访问静态页面毫无问题,一旦涉及到 ...
- Here We Go(relians) Again HDU2722
处理完输入就是很简单的一题 但是输入好难 勉强找到一种能看懂的... #include<iostream> #include<stdio.h> #include<str ...
- Redis中的key的通用操作
1.看看所有的key 2.查看以mys开头的key 3.是否存在 4.删除 5.重命名. 6.设置过期时间与所剩的时间 如果没有设置,返回-1. 7.返回类型
- 062 hive中的常用方法(case,cast,unix_timestamp)
1.case的用法 )格式1 case col when value then '' when value then '' else '' end )格式2 case when col='value' ...
- 移动端Tap与滑屏实战技巧总结以及Vue混合开发自定义指令
最近在忙混合开发,因交互相对复杂,所以也踩了很多坑.在此做一下总结. 1.tap事件的实际应用 在使用tap事件时,老生常谈的肯定是点透问题,大多情况下,在有滑屏交互的页面时,我们会在根节点阻止默认行 ...
- 三篇文章带你极速入门php(三)之php原生实现登陆注册
看下成果 ps:纯天然h5,绝不添加任何添加剂(css)以及化学成分(js)(<( ̄ ﹌  ̄)我就是喜欢纯天然,不接受任何反驳) 关于本文 用原生的php和html做了一个登陆注册,大概是可以窥 ...