通过JDK8源码深入学习String,StringBuffer,StringBuilder
String,StringBuffer,StringBuilder的异同:
1.String为定长的,StringBuffer,StringBuilder为可变长,缓冲区满了会成倍扩容。
2.StringBuffer为线程安全的,StringBuilder为非线性安全。
由stringbuffer的源码可见方法都加上了synchronized锁,势必对性能有所影响。
3.StringBuffer和StringBuilder均继承了AbstractStringBuilder类,StringBuffer和StringBuilder初始化字符串缓存区方法相同。 只举stringbuffer的例子,stringbuilder类似,可见源码。 StringBuffer str1 = new StringBuffer(); 此时初始化构造一个没有字符串的缓存区,初始容量默认为16字符。 StringBuffer str2 = new StringBuffer(str); 此时初始化构造一个内容为str的字符串缓冲区。字符串缓冲区的初始容量是(16+str.length)字符。
4.StringBuffer和StringBuilder的toString()方法均需要占用新的内存,内存大小为字符串的大小。
5.stringbuffer和stringbuilder占用内存以stringbuilder为例:StringBuilder str = new StringBuilder();for (int i=0; i<129; i++){ str.append( "a" );}从默认16字符开始,append()过程中,4次扩容复制 16 + 32 + 64 + 128 + 256 = 496 还不算完,StringBuilder.toString() == return new String(value, 0, count)总共需要 496 + 129 = 525字符,并若干次 内存复制。不仅占用内存还影响GC性能。
优化建议(参考自http://calvin1978.blogcn.com/articles/stringbuilder.html):1.初始化长度非常重要!!!StringBuffer,StringBuilder的内部都有一个缓存区char[],不断的append()就是不断的往char[]里填东西的过程。new StringBuilder() 时char[]的默认长度是16,然后,如果要append第17个字符,怎么办?
用System.arraycopy成倍复制扩容!!!!这样一来有数组拷贝的成本,二来原来的char[]也白白浪费了要被GC掉。可以想见,一个129字符长度的字符串,经过了16,32,64, 128四次的复制和丢弃,合共申请了496字符的数组,在高性能场景下,这几乎不能忍。所以,合理设置一个初始值多重要。但如果我实在估算不好呢?多估一点点好了,只要字符串最后大于16,就算浪费一点点,也比成倍的扩容好。
2.以Liferary的StringBuilder类为例
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就替换。。。
阅读更多
- Java 源码学习线路————_先JDK工具包集合_再core包,也就是String、StringBuffer等_Java IO类库
- String、StringBuilder、 StringBuffer 深入分析 源码解析
- JDK 1.7源码阅读笔记(一)String,StringBuilder,StringBuffer
- 通过源码分析String、StringBuffer和StringBuilder
- JDK源码解析基础篇-String、StringBuilder、StringBuffer
- 【JDK】:java.lang.String、StringBuilder、StringBuffer 源码解析
- 深入源码剖析String,StringBuilder,StringBuffer
- 深入源码剖析String,StringBuilder,StringBuffer
- String,StringBuffer和StringBuilder源码解析[基于JDK6]
- 深入源码剖析String,StringBuilder,StringBuffer
- String、StringBuilder、 StringBuffer 深入分析 源码解析
- Java 深入学习(8) —— String、StringBuilder、StringBuffer的区别
- JDK源码分析之String、StringBuilder和StringBuffer
- java学习之旅56--数组_StringBuilder和StringBuffer的使用_常用方法_方法链的实现_JDK源码分析
- 深入源码剖析String,StringBuilder,StringBuffer
- 深入源码剖析String,StringBuilder,StringBuffer
- 深入java String拼接和StringBuffer、StringBuilder(分析源码)
- JDK1.8源码阅读之——String,StringBuffer, StringBuilder
- JDK源码之String、StringBuffer、StringBuilder
- String、StringBuilder、 StringBuffer 深入分析 源码解析