jdk 源码系列之 StringBuilder、StringBuffer
前言
StringBuilder、StringBuffer 经常使用到,分析 StringBuilder、StringBuffer 源码、通过对比加深对这两个类的了解,以及以后更好的使用。
父类
从上面的继承,我们可以知道,无论是 StringBuilder 亦或是 StringBuffer 都是继承相同的父类 AbstractStringBuilder ,这说明 StringBuilder 、 StringBuffer 他们都是 AbstractStringBuilder 子类,而且操作继承父类的功能相同,差异在于子类实现的不同。我们先来看看 AbstractStringBuilder 的具体实现。
AbstractStringBuilder
先来看看构造方法。
提供两个构造函数,一个是无参构造、一个是有参构造。其中,有参构造,初始化了 char[] 的大小。
了解到这里够了,我们来看看 StringBuilder 以及 StringBuffer 这些子类,具体调了 AbstractStringBuilder 哪些方法。
StringBuilder
提供三种 new 对象
点进去
前面的三种构造、一次调用如上图所示。 调用用了父类的 AbstractStringBuilder 构造构造方法,初始化 char[] 的大小。看来是默认 char[] 是 16。也就是说 StringBuilder 默认大小是16。以及提供初始化 char[] 大小。如果传入的 String 类型,则 char[] 的大小是字符串的长度 + 16。
这里和 HashMap 差不多,都有初始化大小。可能有几个问题,比如使用 StringBuilder 的时候,拼接字符串的长度很小,远远没到 16 的长度。导致空置了许多 char 空间,而这些没用被引用的空间,会触发 GC 回收,进而可能触发 Full GC 影响整个程序性能,所以如果能知道具体长度,尽可能的指定初始化值,优化性能。
接下来点开看 append 源码。
进来的时候,首先判断是不是 null,如果是的话,直接写上 null,标记当前 char 的位置。
使用了类的成员变量 count
其中 value 就是字符存储空间,初始化的使用用到。
count 是记录字符数组的位置。我看了一会,没有找到初始化这个的值,那么这里的 count = 0。
继续, 用到了 ensureCapacityInternal ,看看里面有啥,不过看英文名叫做 确保内部容量。应该是确认容量大小。
当处理的字符串长度,大于当前所能容下的大小,则以 当前容量 * 2 + 2 大小扩容,反之则不能处理。同时进行数组间的复制。这也是什么 StringBuilder 能动态拼接字符串,而不是固定大小就不能再申请的原因。
当超过所能容纳的最大字符时,2 ** 31 - 9 (MAX_VALUE = 2 **31 -1,另外它自己 - 8) 的大小,则直接 OOM。
由于这里存在动态扩容,char[] 的空间也可能存在空置,未被使用,引起 GC 回收。同时这里还有数组间的复制,导致性能有所下降,所以还是能确定大小,则直接初始化大小。
继续 append 方法。
appendNull 和 append 差不过,每次都在判断是否需要扩容,然后记录 char[] 的位置。
最后,一般我们都要将 StringBuilder toString()。
输出的时候,直接使用 char[] 数组,从 0 到 count 所记录的位置,产生一个对象 String 返回。
另外。
StringBuilder 还有提供了许多有趣的东西。
字符串的反转
与 StringBuilder 类拼接
插入某个位置
以及返回当前数组位置 等等
StringBuilder 的总结
成员变量 count 记录数组位置,使用的是 int 类型,在多线程中未涉及到原子性,可能导致 count 的数值有误,从而导致最后输出的字符串有问题。
初始化的时候最好指定大小,避免触发 GC、以及数组之间的复制操作。
StringBuffer
也是提供三个构造方法。和 StringBuilder 没去。都是默认16,也可以自行初始化、或者直接写字符串。
不过在还新增了一个成员变量 toStringCache
根据 transient 的特性,我们可以知道这个 toStringCache 不会持久化,作为一个缓冲的存在。感觉这样做的原因是,这个值一直在改变,只有等到最后输出的时候才用到。可能节省空间吧。
另外除了构造方法,所有的方法都加上了 synchronized 修饰,来保证线程安全。所有的方法和 StringBuilder 差不多。
StringBuffer 总结
线程安全。
由于所有的方法都加上了 synchronized 所以效率是远满于 StringBuilder 的拼接速度。
总结
StringBuffer vs StringBuilder
优化建议
尽可能的指定初始化大小,避免频繁的扩容、以及数组之间的复制,到而导致空置数组空间,触发 GC。
确定业务模型之后,是单线程或者业务并发不高,可以选择 StringBuilder,来拼接字符串。
高并发底下,请选择 StringBuffer 来拼接字符串。性能差可以接受,但是出问题,是不能容忍的。
声明
作者: Sinsy
本文链接:https://blog.sincehub.cn/2020/09/29/jdk-StringBuilder-StringBuffer/
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文声明。
如您有任何商业合作或者授权方面的协商,请给我留言:550569627@qq.com
版权声明: 本文为 InfoQ 作者【苏格兰、情调】的原创文章。
原文链接:【http://xie.infoq.cn/article/4dbe57a74fe69ddefeb9a0185】。文章转载请联系作者。
评论