1 StringBuffer与StringBuilder
StringBuffer与StringBuilder都是final类,不能被继承。
上图是二者的UML图,可以看出,它们都继承了抽象类AbstractBuilder。AbstractBuilder定义了StringBuffer与StringBuilder的基本操作。
这是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;
}
从这三个方法可以看出,StringBuffer和StringBuilder通过继承,直接调用了父类中方法的实现,那这二者有什么区别呢?
StringBuffer和StringBuilder最大的区别在于:StringBuffer是线程安全的,它的相关方法都加了锁synchronized,StringBuilder是线程不安全的,在单线程环境中,使用StringBuilder比使用StringBuffer效率要高。
2 StringBuffer与String的效率问题
先上一段测试代码:
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);
}
}
从反编译的代码中可以看出:
1 String s5 = "s1" + "s2" + "s3" + "s4"
经编译器优化后,变为
String s5 = "s1s2s3s4";
故这二者是等价的
2 String s6 = s1 + s2 + s3 + s4;
反编译之后得到的代码为:
String s6 = (new StringBuilder(String.valueOf(s1))).append(s2).append(s3).append(s4).toString();
已然很明确了,通过“+”进行字符串拼接实际上是调用了StringBuilder的append方法。
这样这两种拼接字符的方法效率就是相同的了。
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()方法在没有找到相同值的对象时,是把当前对象加入字符串池中,然后返回它的引用的话,那么b和a指向的就是同一个对象;否则b指向的对象就是JAVA虚拟机在字符串池中新建的,只是它的值与a相同罢了。上面这段代码的运行结果恰恰印证了这一点。
错误之处,请指正,共同学习,谢过!