StringBuffer、StringBuilder与String剖析

1 StringBuffer与StringBuilder

StringBufferStringBuilder都是final类,不能被继承。


上图是二者的UML图,可以看出,它们都继承了抽象类AbstractBuilderAbstractBuilder定义了StringBufferStringBuilder的基本操作。

这是append方法实现代码:

 public AbstractStringBuilder append(String str) {
	if (str == null) str = "null";
        int len = str.length();
	if (len == 0) return this;
	int newCount = count + len;
	if (newCount > value.length)
	    expandCapacity(newCount);
	str.getChars(0, len, value, count);
	count = newCount;
	return this;
}
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > count) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, offset + srcBegin, dst, dstBegin,
             srcEnd - srcBegin);
    }



这是AbstractStringBuilder 中的append方法,实现原理很简单,AbstractStringBuilder 内部数据结构是字符数组,append方法先判断字符数组长度是否够,如果不够就创建一个更大的字符数组,将原有字符和新字符复制到新建数组中。复制使用的是System类中的arrayCopy方法。

StringBuffer中的append方法:

 public StringBuffer(String str) {
	super(str.length() + 16);
	append(str);
}

StringBuilder中的append方法:

public StringBuilder append(String str) {
	super.append(str);
        return this;
}

从这三个方法可以看出,StringBufferStringBuilder通过继承,直接调用了父类中方法的实现,那这二者有什么区别呢?

StringBufferStringBuilder最大的区别在于:StringBuffer是线程安全的,它的相关方法都加了锁synchronizedStringBuilder是线程不安全的,在单线程环境中,使用StringBuilder比使用StringBuffer效率要高。

2 StringBufferString的效率问题

先上一段测试代码:

class Test {
	public static void main(String args[]) {
		String s1 = "s1";
		String s2 = "s2";
		String s3 = "s3";
		String s4 = "s4";
		String s5 = "s1" + "s2" + "s3" + "s4";
		String s6 = s1 + s2 + s3 + s4;
		StringBuffer buf = new StringBuffer();
		buf.append(s1).append(s2).append(s3).append(s4);
	}
}

下面是使用DJ Java DCompiler3.7反编译Text.class得到的结果:

class Test{
Test(){}
public static void main(String args[]){
 String s1 = "s1";
 String s2 = "s2";
 String s3 = "s3";
 String s4 = "s4";
 String s5 = "s1s2s3s4";//对应 String s5="s1"+"s2"+"s3"+"s4"
 String s6 = (new StringBuilder(String.valueOf(s1))).append(s2).append(s3).append(s4).toString();//对应String s6=s1+s2+s3+s4;
 StringBuffer buf = new StringBuffer();
 buf.append(s1).append(s2).append(s3).append(s4);
    }
}

从反编译的代码中可以看出:

String s5 = "s1" + "s2" + "s3" + "s4"

经编译器优化后,变为

String s5 = "s1s2s3s4";

故这二者是等价的

String s6 = s1 + s2 + s3 + s4;

反编译之后得到的代码为:

String s6 = (new StringBuilder(String.valueOf(s1))).append(s2).append(s3).append(s4).toString();

已然很明确了,通过“+”进行字符串拼接实际上是调用了StringBuilderappend方法。

这样这两种拼接字符的方法效率就是相同的了。

String str =""; 
for(int i=0;i<100;i++){   
	str+=i;  
} 
 StringBuffer sb =new  StringBuffer();
   for(int i=0;i<100;i++){   
	sb.append(i);   
  } 

如果是这种情况,显然是使用StringBuffer效率要高,前者每次使用字符串拼接都会创建一个StringBuilder对象,然后使用toString方法得到拼接后的字符串,显然多做了一些工作。

关于String的特殊性介绍,请看博客String is special点击打开链接

这里补充一些内容:

1、String s = new String("abc");这句代码究竟创建了几个String对象

这段代码调用的是这样的构造方法:

public String(String original) {
   //other code …
}

所以,String s = new String("abc")可以分解为两步:

1 String original = "abc";

2 String s = new String(original );

这样,显然是创建了两个对象。至于String original = "abc"这句代码的原理,String is special中已经讲得很清楚了。

2、public native String intern();

这是一个本地方法。在调用这个方法时,JAVA虚拟机首先检查字符串池中是否已经存在与该对象值相等对象存在,如果有则返回字符串池中对象的引用;如果没有,则先在字符串池中创建一个相同值的String对象,然后再将它的引用返回。

public class StringInternTest {
public static void main(String[] args) {
// 使用char数组来初始化a,避免在a被创建之前字符串池中已经存在了值为”abcd”的对象
    String a = new String(new char[] { ‘a’, ‘b’, ‘c’, ‘d’ });
    String b = a.intern();
    if (b == a) {
        System.out.println(“b被加入了字符串池中,没有新建对象”);
    } else {
        System.out.println(“b没被加入字符串池中,新建了对象”);
    }
 }
}

运行结果:

b没被加入字符串池中,新建了对象

如果String类的intern()方法在没有找到相同值的对象时,是把当前对象加入字符串池中,然后返回它的引用的话,那么ba指向的就是同一个对象;否则b指向的对象就是JAVA虚拟机在字符串池中新建的,只是它的值与a相同罢了。上面这段代码的运行结果恰恰印证了这一点。

错误之处,请指正,共同学习,谢过!



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章