关于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()

// Create a copy, don't share the array
return new String(value, 0, count);

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

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

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

4. 重用StringBuilder

这个做法来源于JDK里的BigDecimal类(没事看看JDK代码多重要),SpringSide里将代码提取成StringBuilderHolder,里面只有一个函数

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

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

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

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。而且重用之后,就不用再玩猜长度的游戏了。

Java 中 StringBuilder 在高性能用法总结的更多相关文章

  1. java中stringBuilder的用法

    java中stringBuilder的用法 String对象是不可改变的.每次使用 System.String类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间.在需 ...

  2. Java中的Socket的用法

                                   Java中的Socket的用法 Java中的Socket分为普通的Socket和NioSocket. 普通Socket的用法 Java中的 ...

  3. Java中Date各种相关用法

    Java中Date各种相关用法(一) 1.计算某一月份的最大天数 Java代码 Calendar time=Calendar.getInstance(); time.clear(); time.set ...

  4. JAVA中enum的常见用法

    JAVA中enum的常见用法包括:定义并添加方法.switch.遍历.EnumSet.EnumMap 1.定义enum并添加或覆盖方法 public Interface Behaviour{ void ...

  5. 巨人大哥谈Java中的Synchronized关键字用法

    巨人大哥谈Java中的Synchronized关键字用法 认识synchronized 对于写多线程程序的人来说,经常碰到的就是并发问题,对于容易出现并发问题的地方价格synchronized基本上就 ...

  6. Java中Class类及用法

    Java中Class类及用法 Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识,即所谓的RTTI.这项信息纪录了每个对象所属的类.虚拟机通常使用运行时类型信息选准正确方 ...

  7. JAVA中mark()和reset()用法

    根据JAVA官方文档的描述,mark(int readlimit)方法表示,标记当前位置,并保证在mark以后最多可以读取readlimit字节数据,mark标记仍有效.如果在mark后读取超过rea ...

  8. java中class,public的用法

    java中class,public的用法 一.Java访问权限饰词(access specifiers) Java有public.protect.friendly.private四种访问权限,并且这四 ...

  9. java中equals以及==的用法(简单介绍)

    简单介绍 equals方法是java.lang.Object类的方法 有两种用法说明: 一.对于字符串变量来说,使用“==”和“equals()”方法比较字符串时,其比较方法不同. 1.“==”比较两 ...

随机推荐

  1. Junit4_单元测试

    不多说,直接练习学习. 1.将Junit4单元测试包引入项目:项目右键——“属性”,选择“Java Build Path”,然后到右上选择“Libraries”标签,之后在最右边点击“Add Libr ...

  2. hdoj 1089(费马小定理)

    题目大意:方程f(x)=5*x^13+13*x^5+k*a*x:输入任意一个数k,是否存在一个数a,对任意x都能使得f(x)能被65整出. 现假设存在这个数a ,因为对于任意x方程都成立 所以,当x= ...

  3. 关于typedef的用法总结

    不管实在C还是C++代码中,typedef这个词都不少见,当然出现频率较高的还是在C代码中.typedef与#define有些相似,但更多的是不同,特别是在一些复杂的用法上,就完全不同了,看了网上一些 ...

  4. PHP+MySQL中对UTF-8,UTF8(utf8),set names gbk 的理解

    问题一:在我们进行数据库操作时会发现,数据库中表的编码用的是utf-8,但是在进行dos命令是要使用set names gbk (一)Mysql中默认字符集设置有四级:服务器级,数据库级,表级,和字段 ...

  5. C# winform 递归选中TreeView子节点

    /// <summary> /// 递归选中所有的自节点 /// </summary> /// <param name="nodeThis">T ...

  6. 微信小程序,大多数人误解的8个问题

    作者:王安,数字天堂DCloud公司创始人兼CEO 注:本文内容包含技术.商业,不懂技术的读者可以只看商业相关的内容.本文仅代表作者一家之言,如有不同意见,欢迎留言讨论~ 8个误解 坊间所传的信息很多 ...

  7. JDK环境变量配置贺Tomcat环境搭建

    一.安装JDK JDK (Java Develpmet kit) Java开发环境(工具包和运行环境jre) 是Java开发的核心,包括:编译程序的命令 javac 运行程序java命令 he jav ...

  8. PHPCMS V9调用时间标签 |日期时间格式化

    PHPCMS V9 如何调用时间标签,下面分享常见的调用时间标签 |日期时间格式化  1.日期时间格式化显示: a标准型:{date('Y-m-d H:i:s', $rs['inputtime'])} ...

  9. MySQL zip版安装配置

    文章出处:http://www.cnblogs.com/winstic/,请保留此连接 这段时间在学习Python 数据库操作知识,简单整理MySQL zip文件安装方法 下载 在MySQL官网htt ...

  10. WEB工程数据库相关安装脚本写作

    1. 数据库oracle安装 2. 数据库用户创建,表空间创建,表创建 #!/bin/bash current_path=`pwd` create_tablespace=${current_path} ...