java中的String不再纠结(续)
很早之前总结过java中一些String的理解和用法,最后还体会到了其中String的一点性能上的优化。那篇博文更多的是在讨论string存储的问题,感兴趣的童鞋可以看一下 传送连接
这两天在淘测试的文章里看到一篇关于java string的文章,谈到了StringBuilder和StringBuffer的使用效率的问题,然后发现自己忽略了capacity这个概念。比如说下面的一段代码:
1 StringBuffer sf = new StringBuffer(""); 2 sf.append("leeon"); 3 System.out.println("length: "+sf.length()); 4 System.out.println("capacity: "+sf.capacity());
这个打印的结果就是
length:5
capacity:16
length我们可以理解,那么capacity是什么呢?
JDK源码中给出的解释就是用来存储新插入的字符空间的容量,可见一个默认的容量就是16了,当然如果我们像下面这样的去重写上面的代码,就会得到不一样的结果。
1 StringBuffer sf = new StringBuffer("leeon"); 2 System.out.println("length: "+sf.length()); 3 System.out.println("capacity: "+sf.capacity());
这次的结果就是
length:5
capacity:21
原因就是我们在构造sf的时候本身的value已经是5了,再加上了新准备插入的初始化容量,也就是5+16.具体实现的细节是:
----------------------------------------------------------------------------------------------------------
以上是背景知识,下面开始讨论如何从capacity这个特性出发来优化性能。再仔细阅读jdk源码中append方法的实现的时候会发现,每次调用append都要进行容量的检查,因为要确保StringBuffer足够的大才能装得下新添加的字符串。不妨再一起看下代码:
当调用append的时候,首先会通过ensureCapacityInternal检查所需的容量,如果容量不足再调用expandCapacity进行扩容,可以看见扩容的方式是将现在的字符串大小增加一倍然后再加上2.如果容量仍然不足,则直接扩展到所需的大小。我们发现扩展容量就是一个耗时的操作,虽然处理大量字符串的时候采用StringBuffer会比用String更加的提升性能,但是却仍然存在可优化的耗时部分。
其实StringBuffer和StringBuilder在初始化的时候是可以指定capacity的,如果有一个大概的预估,初始化一个比较合适的capacity,减少扩展容量的操作,效率就会有更大的提高。比如下面的测试代码,我们对同样的字符串操作五百万次,统计一下结果。
1 StringBuilder sb = new StringBuilder(10*5000000); 2 StringBuffer sf = new StringBuffer(10*5000000); 3 4 5 long start = System.currentTimeMillis(); 6 for(int i = 0; i < 5000000; i++) 7 { 8 sb.append("1234567890"); 9 } 10 long end = System.currentTimeMillis(); 11 System.out.println("the StringBuilder run time is "+(end -start)+" ms"); 12 13 start = System.currentTimeMillis(); 14 for(int i = 0; i < 5000000; i++) 15 { 16 sf.append("1234567890"); 17 } 18 end = System.currentTimeMillis(); 19 System.out.println("the StringBuffer run time is "+(end -start)+" ms");
上面的结果我没有求平均值,测试也只是在单线程下进行的,但是数据波动不大,大致可以看出一些变化,我们发现当恰当的初始化了StringBuffer后,他的性能已经和未初始化的StringBuilder相当甚至超过了,这样我们就能够在保持和原来StringBuilder相当效率的基础上有更好的安全性了。
可以看到采用了初始化的容量,我们获得的性能提升要高于从SF切换到SB。
淘测试总结的结论中有一个我很喜欢,引用一下
“ 用好现有的类比引入新的类更重要。很多程序员在使用 StringBuffer 时是不指定其容量的(至少我见到的情况是这样),如果这样的习惯带入 StringBuilder 的使用中,你将只能获得 10 %左右的性能提升(不要忘了,你可要冒多线程的风险噢);但如果你使用指定容量的 StringBuffer ,你将马上获得 45% 左右的性能提升,甚至比不使用指定容量的 StirngBuilder 都快 30% 左右。”
这个问题其实后续还值得讨论,现在我们看到都是单线程的情况,那么多线程情况下,StringBuffer是不是可以优化的更多了?
忽然发现,自己忽略了很多java的特性,学在当下。