深入StringBuffer
作者:eckel_cn
最近,闲暇之余,重新整理自用的StringUtil类,在整理到repeat方法(重复字符串)时,发现其存在效率问题,便打算重新写一下,却发现了一系列以前没太注意的东西,觉得很有必要写下来.
初一看,就能发现一点问题,当n很大,达到一定大小,就能使堆栈溢出.出现OutOfMemery错误.
那如何避免这个问题呢,我马上想到了,既然StringBuffer和String一样内部都是在操作char数组,
那我就自己直接操作char数组,在最少的循环里面,填充char数组,代码如下:
1public static String repeat2(String str, int n) {
2 int len = str.length();
3 int maxlen = len * n;
4 char[] chars = new char[len * n];
5 str.getChars(0, len, chars, 0);
6 int count = len;
7 int copyLen = 0;
8 while ((copyLen = maxlen - count > count ? count : maxlen - count) > 0) {
9 System.arraycopy(chars, 0, chars, count, copyLen);
10 count += copyLen;
11 }
12 String outstr = new String(chars);
13 return outstr;
14}
测试后,发现,循环控制在了最少,但是,性能却还不如repeat1,也许你会和我一样开始很惊讶,但看了
StringBuffer的实现后,发现repeat2的实现中,将char数组,转换为String,是效率多么低的事情,这个过程需要在String内部重新创建一个char数组,然后把传进去的char数组,复制给它.而repeat1中为什么没有这个性能问题呢,其实这就是要归功于SUN在实现StringBuffer中的一个重要属性:shared,当你使用StringBuffer后,想得到String时,String会共享StringBuffer中的char数组,这样一来,性能非常高.
共享会不会带来副作用呢,SUN的实现当然考虑到了,在改变StringBuffer时,如果这个StringBuffer
有其他String共享它的char数组时,StringBuffer就把这个char数组让给String,自己重新复制一份使用.
既然问题发现了,我又换了种算法来实现,同样也是把循环控制在最少,同时解决转换带来的效率低下问题.
1public static String repeat3(String str, int n) {
2 // if input string is null,return null.
3 if (null == str) {
4 return null;
5 }
6 final int strlen = str.length();
7 // if repeat number is less than two or given string's length is zero,
8 // then return the givien string.
9 if (2 > n || 0 == strlen) {
10 return str;
11 }
12 // assigns the enough size for the string buffer.
13 StringBuffer strBuf = new StringBuffer(strlen * n);
14 // get odd length flag.
15 final boolean oddLen = n % 2 == 1 ? true : false;
16 // calculates the grow times.
17 final int growTimes = (int) Math.floor(Math.log(n) / Math.log(2));
18 // adds one given string.
19 strBuf.append(str);
20 // grows until more than half of the repeat count.
21 for (int i = 0; i < growTimes; i++) {
22 strBuf.append(strBuf);
23 if (Math.pow(2, i - 1) * 2 < n && Math.pow(2, i) * 2 >= n) {
24 break;
25 }
26 }
27 // reset the string buffer's length to half of the repeat count.
28 strBuf.setLength(strlen * Math.round(n / 2));
29 // grows to the repeat count.
30 strBuf.append(strBuf);
31 if (oddLen) {
32 strBuf.append(str);
33 }
34 String returnStr = new String(strBuf);
35 return returnStr;
36}
37 算法实现,再次测试,测试发现,在规模不是特别大的情况下,repeat3的效率要明显优于前两个,但在规模大的情况下,会变的差不多,怎么会这样的呢,再次进行深入调查,结果发现是SUN的System.arrayCopy这个方法有性能问题,特别是当要复制的长度特别大的情况下,性能下降的比较快,再换个IBM的JDK实现,又发现,IBM的这个方法的实现要比SUN有改进.
真是一次比较有意思的深入StringBuffer,在实际运用中repeat3完全可以代替上面两种方法.