Oo缘来是你oO


posts - 120,comments - 125,trackbacks - 0

如何让你的程序运行的更快(1)之续---揭秘StringBuffer的capacity


马嘉楠 2006-09-19

前几天写了一篇文章“ 如何让你的程序运行的更快(1)---String VS StringBuffer ”,文章在情景三中提到了如何通过“设置StringBuffer的容量来提升性能”,其中有个问题我没有想明白,就是为什么StringBuffer的容量自动增加的时候是“2*旧值+2”呢

虽然问题依然没有解决,不过也发现了不少有趣的问题,在此和大家分享 。希望能让你有所收获,欢迎大家一起讨论。

注:需要用到的函数说明:
capacity():Returns the current capacity of the String buffer. 
                     The capacity is the amount of storage available for newly inserted characters;  
                      beyond which an allocation will occur.
length():   Returns the length (character count) of this string buffer.

 

一.StringBuffer的默认capacity
例1:

StringBuffer sb  =   new  StringBuffer();
System.out.println( " with no characters, the initial capacity of StringBuffer is  "   +
 sb.capacity());
System.out.println( " and the length of the StringBuffer is  "   +  sb.length());

输出:

with no characters, the initial capacity of StringBuffer is  16
and the length of the StringBuffer is 
0

结论: StringBuffer的默认容量(capacity)为16

原因:
StringBuffer的默认构造函数为

public   StringBuffer() {
      
this ( 16
 );

此时默认构造函数又调用了StringBuffer的代参数的构造函数,设置字符串数组value长度为16,如下:

private   char  value[];                //The value is used for character storage.
private   boolean  shared;         // A flag indicating whether the buffer is shared

public  StringBuffer( int  length) {
      value 
=   new   char
[length];
      shared 
=   false
;
}
 
// 调用capacity()返回字符串数组value的长度,即StringBuffer的容量(capacity)

public   synchronized   int  capacity() {
      return
 value.length;
}



二.用字符串初始化StringBuffer的内容

在声明一个StringBuffer变量的时候,用字符串进行初始化,容量会有变化么?

例2:

// 声明并初始化
StringBuffer sb1  =   new  StringBuffer( " hello world "  );
System.out.println(
" with characters, the capacity of StringBuffer is  "   +
  sb1.capacity());
System.out.println(
" but the length of the StringBuffer is  "   +
 sb1.length()); 

// 利用append()来设置StringBuffer的内容

StringBuffer sb11  =   new  StringBuffer();
sb11.append(
" hello world "
);
System.out.println(
" with append(), the capacity of StringBuffer is  "   +
 sb11.capacity());
System.out.println(
" but the length of the StringBuffer is  "   +  sb11.length());


两者输出结果会一样么?
你一定认为,这不是显然的么。用长度为11的字符串“hello world”进行初始化,其长度11小于StringBuffer的默认容量16。所以两者结果都为capacity=16,length=11。
那么实际结果如何呢?

输出:

with characters, the capacity of StringBuffer is 27
but the length of the StringBuffer is 11
with append(), the capacity of StringBuffer is 16
but the length of the StringBuffer is 11

疑问:
怎么第一种方法的StringBuffer的capacity是27(16+11)呢?

原因:
StringBuffer的带参数的构造函数

1 public  StringBuffer(String str) {
2       this(str.length() + 16
);
3       
append(str);
4 }

结论:
StringBuffer的capacity等于用来初始化的字符串长度(11)加上StringBuffer的默认容量(16),而不是我们想当然的在默认容量16中拿出11个来存放字符串“hello world”。
如果我们不设置StringBuffer的capacity,分别对两者继续追加字符串,任其自动增长,其容量增长如下:
第一种情况:27,56,114,230,462,926...,
第二种情况:16,34,70  ,142,286,574...,
(为什么容量增加会是这种规律,后面会做解释)。

我想情况2节省空间的概率大一些,因为StringBuffer的capacity的增长比情况1慢,每次增加的空间小一些。
所以以后写代码的时候可以考虑使用第二种方法(使用StringBuffer的append()),特别是初始化字符串很长的情况。当然这会多写一行代码^+^:


三.StringBuffer的capacity变化

例3:

StringBuffer sb3  =   new  StringBuffer();
for ( int  i = 0 ; i < 12 ; i ++
){
      sb3.append(i);
}
System.out.println(
" before changed, the capacity is  "   +
 sb3.capacity());
System.out.println(
" and the length is  "   +
 sb3.length()); 

for ( int  i = 0 ; i < 10 ; i ++
){
      sb3.append(i);
}
System.out.println(
" first time increased, the capacity is  "   +
 sb3.capacity());
System.out.println(
" and the length is  "   +
 sb3.length()); 

for ( int  i = 0 ; i < 11 ; i ++
){
      sb3.append(i);
}
System.out.println(
" second time increased, the capacity is  "   +
 sb3.capacity());
System.out.println(
" and the length is  "   +
 sb3.length()); 

输出:

before changed, the capacity is  16
and the length is 
14
first time increased, the capacity is 
34
and the length is 
24
second time increased, the capacity is 
70
and the length is 
36  

奇怪,benfore changed怎么长度不是12而是14呢?哈哈,开始我也困惑了一下,仔细想想就会明白的,你可以输出sb3看看System.out.println("the content of sb3 is " + sb3.toString());

结论:
capacity增长的规律为 (旧值+1)*2

原因:
StringBuffer的容量增加函数expandCapacity():

private   void  expandCapacity( int  minimumCapacity) {
int  newCapacity  =  (value.length  +   1 *   2
;
if  (newCapacity  <   0
) {
      newCapacity 
=
 Integer.MAX_VALUE;
else   if  (minimumCapacity  >
 newCapacity) {
      newCapacity 
=
 minimumCapacity;
}
 
char  newValue[]  =   new   char
[newCapacity];
System.arraycopy(value, 
0 , newValue,  0
, count);
value 
=
 newValue;
shared 
=   false
;

疑问:
为什么要(旧值+1)*2呢?

我自己的想法:
也许是考虑到value.length的值可能为0(初始化时设置StringBuffer的capactity为0).
或者考虑到溢出的情况?
但是可能是JVM的某些限制,我的机器数组最大可以设置为30931306,再大就报错:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

30931306这个数字好奇怪

还有哪方面的考虑么?谁知道,能告诉我么?


四.StringBuffer的capacity只能大不能小

例5:

 1 StringBuffer sb4 = new  StringBuffer();
 2 System.out.println("before ensureCapacity(), the capacity is " +
 sb4.capacity());
 3 sb4.ensureCapacity(10
);
 4 System.out.println("after ensureCapacity(10), the capacity is " +
 sb4.capacity());
 5 
        
 6 System.out.print("now, the capacity is " + sb4.capacity() + ""
);
 7 sb4.ensureCapacity(20
);
 8 System.out.println("after ensureCapacity(20), the capacity is " +
 sb4.capacity());
 9 
        
10 System.out.print("now, the capacity is " + sb4.capacity() + ""
);
11 sb4.ensureCapacity(80
);
12 System.out.println("after ensureCapacity(80), the capacity is " + sb4.capacity());

输出:

before ensureCapacity(), the capacity is  16
after ensureCapacity(
10 ), the capacity is  16
now, the capacity is 
16 , after ensureCapacity( 20 ), the capacity is  34
now, the capacity is 
34 , after ensureCapacity( 80 ), the capacity is  80

结论:
当设置StringBuffer的容量
1、小于当前容量时,容量不变。
      本例中,容量依然为16。
2、大于当前容量,并且小于(当前容量+1)*2,则容量变为(当前容量+1)*2。
      本例中,16<20<(16+1)*2=34,所以容量为34。
3、大于当前容量,并且大于(当前容量+1)*2,则容量变为用户所设置的容量。
      本例中,80>16,80>(16+1)*2=34,所以容量为80。

原因:
函数:ensureCapacity( )和 expandCapacity( )进行了控制

public   synchronized   void  ensureCapacity( int  minimumCapacity) {
      
if  (minimumCapacity  >
 value.length) {
// 当设置StringBuffer的容量小于当前容量时,容量不变。

            expandCapacity(minimumCapacity);
      }
}

private   void  expandCapacity( int
 minimumCapacity) {
      int  newCapacity  =  (value.length  +   1 *   2
;
      if  (newCapacity  <   0
) {
            newCapacity 
=
 Integer.MAX_VALUE;
      } 
else   if  (minimumCapacity  >
 newCapacity) {
      //
当设置StringBuffer的容量大于(当前容量+1)*2,则容量变为用户所设置的容量。
      // 否则,容量为(当前容量+1)*2,即newCapacity

            newCapacity  =  minimumCapacity;
      }
 
char  newValue[]  =   new   char
[newCapacity];
System.arraycopy(value, 
0 , newValue,  0
, count);
value 
=
 newValue;
shared 
=   false
;


 
注:
问一下
String str = String.valueOf(null);
System.out.println(str.length());

你们执行的话会出错么?
我的报错,我的是jdk1.4

因为StringBuffer中的append(String str)函数中有这样的语句,

public   synchronized  StringBuffer append(String str) {
if  (str  ==   null
) {
      str 
=
 String.valueOf(str);
}

int  len  =  str.length(); // len有可能得负值么?

int  newcount  =  count  +  len;
if  (newcount  >
 value.length)
expandCapacity(newcount);
str.getChars(
0
, len, value, count);
count 
=
 newcount;
return   this
;
}

 



马嘉楠
jianan.ma@gmail.com

posted on 2006-09-20 17:05 马嘉楠 阅读(2454) 评论(5)  编辑  收藏

FeedBack:
# re: 如何让你的程序运行的更快(1)之续---揭秘StringBuffer的capacity
2006-09-29 14:17 | 肉包
String str = String.valueOf(null);
-- >
String str = String.valueOf((Object)null);
  回复  更多评论
  
# re: 如何让你的程序运行的更快(1)之续---揭秘StringBuffer的capacity
2006-09-29 14:43 | 肉包
JDK1.5
String.valueOf(null)失败  回复  更多评论
  
# re: 如何让你的程序运行的更快(1)之续---揭秘StringBuffer的capacity
2006-09-29 14:49 | 肉包
是StringBuffer append方法,和String valueOf有关系么
-.-"  回复  更多评论
  
# re: 如何让你的程序运行的更快(1)之续---揭秘StringBuffer的capacity
2006-09-29 14:56 | 肉包
System.out.println(null instanceof Object); //false
StringBuffer str = new StringBuffer().append((StringBuffer)null); //pass
StringBuffer str1 = new StringBuffer().append((String)null); //pass
StringBuffer str1 = new StringBuffer().append(null); //throw exception
System.out.println(String.valueOf((Object)null).length()); //4  回复  更多评论
  
# re: 如何让你的程序运行的更快(1)之续---揭秘StringBuffer的capacity
2012-04-10 10:31 | KingOfLightArmy
public synchronized StringBuffer append(String str) {
if (str == null ) {
str = String.valueOf(str);
}

int len = str.length(); // len有可能得负值么?
int newcount = count + len;
if (newcount > value.length)
expandCapacity(newcount);
str.getChars( 0 , len, value, count);
count = newcount;
return this ;
}
上面的 str = String.valueOf(str);没有问题,原因在于,即使str是null,这个null是(String)null,本质是String.valueOf((String)null),所以不会出现异常。这和 String.valueOf(null),是有区别的,因为null不属于任何类型,所以valueOf这个有多个重载方法,但是都无法匹配null,除非转型(Object)null。  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: