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相同罢了。上面这段代码的运行结果恰恰印证了这一点。
错误之处,请指正,共同学习,谢过!