关于Java中String-StringBuilder-StringBuffer的区别

    

 a.jpg
    年初-面试的旺季,我也不例外,最近也是在准备面试的资料,从中我也发现有几个是比较常问的,在以后的文章中也会去逐一分享,今天主要就是谈谈String-StringBuilder-StringBuffer三者间的区别。
    首先,“字符串”,提起这个词我们首先会想到String,而String在我们Java中属于不可变的类,因为它一旦实例化后,那么你对它的修改其实都是创建了一个新的对象(final修饰)。当然,这也并非没有好处,由于它的不可变,在它创建的时候会去缓存HashCode值(这点我也不太懂),这也意味着不需要去重新计算,并且在速度上使用字符串作为Map集合的键往往比使用其他类型要快,比如HashMap中的键绝大多数使用的都是字符串。
    结合上面所述(String不可变),我们可以猜到:所谓的String字符串拼接其实就是创建了新的String对象。
    而在我们Java中使用的字符串拼接基本是以下5种:

  1. 使用 + 拼接字符串
  2. 使用 concat() 方法 拼接字符串
  3. 使用StringBuffer的 append() 方法拼接
  4. 使用StringBuilder的 append() 方法拼接
  5. 使用StringUtils的join()方法(这个不常用)

我们先从代码层次简单分析一下他们的方法执行:
    首先是+号拼接:
//使用+进行拼接
String str1 = "X";
String str2 = "U";
System.out.println(str1+str2+"!");
我们将代码反编译后,显示如下代码:
String str1 = "X";
String str2 = "U";
System.out.println((new StringBuilder()).append(str1).append(str2).append("!").toString());
    从这里我们可以看出,String进行+号拼接时会去创建StringBuiler对象,并调用其append方法,然后在调用toString进行进行赋值,那么它在内存中的引用地址其实已经发生改变。
    这次我们再试试concat方法:
//使用concat拼接
System.out.println(str1.concat(str2).concat("?"));
这次我们将代码反编译后,显示如下代码:
System.out.println(str1.concat(str2).concat("?"));
    跟String+拼接不一样的是,concat拼接似乎不会去创建新的对象,显示的依旧是concat方法。但是我们去查看一下String类的源码,找到其concat方法,如下:
public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }
    我们会发现,它首先得到了拼接字符的长度,然后判断,如果为0则直接返回源字符(拼接字符长度为0也就代表着没有东西来拼接-没意义),接着获得了源字符的长度, 然后创建了一个char数组且长度为源字符和拼接字符长度之和,数组值为源字符。又调用了getChars方法,将自身插入到名为buf的char数组中,且插入位置为源字符的长度(就是跟在后面),最后返回了一个新的String对象(值为拼接后的char数组),这也意味着通过.concat方法进行拼接也会去创建新的对象。
    下面我们再试试StringBuilder和StringBuffer的apped方法拼接:
//append拼接
StringBuilder b1 = new StringBuilder("B");
System.out.println(b1.append("O"));
StringBuffer b2 = new StringBuffer("K");
System.out.println(b2.append("E"));
反编译后的代码没什么变化,我们再去看看append方法的具体实现:
public StringBuilder append(String str) {
        super.append(str);
        return this;
} 
这里我们可以看到其调用了父类AbstractStringBuilder的append方法,代码如下:
public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
 }
    虽然这个方法有很多重载形式,但是我们可以看到其最终并没有新建对象,而且返回的还是其本身。
我们再来看看StringBuffer的append方法:
public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
}
    同样是调用父类方法,但是StringBuffer跟StringBuilder的继承一样,都是AbstractStringBuilder,唯一的不同就是StringBuffer在自身的append方法前加了synchronized关键字用来控制线程安全。
代码分析完了,我们再来看看循环拼接10000个“A”时,每种方式的性能耗时:
StringBuilder b1 = new StringBuilder("B");
        StringBuffer b2 = new StringBuffer("K");
        String str1 = "";
        String str2 = "";
        long startTime;
        long endTime;

        startTime = System.currentTimeMillis();
        for (int i=0;i<10000;i++){
            str1 = str1 + "A";
        }
         endTime = System.currentTimeMillis();
        System.out.println("+共耗时:"+(endTime-startTime)+"ms");
        startTime = System.currentTimeMillis();
        for (int i=0;i<10000;i++){
            str2 = str2.concat("A");
        }
         endTime = System.currentTimeMillis();
        System.out.println("concat共耗时:"+(endTime-startTime)+"ms");
        startTime = System.currentTimeMillis();
        for (int i=0;i<10000;i++){
            b1 = b1.append("A");
        }
        endTime = System.currentTimeMillis();
        System.out.println("StringBuilder共耗时:"+(endTime-startTime)+"ms");
        startTime = System.currentTimeMillis();
        for (int i=0;i<10000;i++){
            b2 = b2.append("A");
        }
        endTime = System.currentTimeMillis();
        System.out.println("StringBuffer共耗时:"+(endTime-startTime)+"ms");
程序最终输出结果:
+共耗时:214ms
concat共耗时:37ms
StringBuilder共耗时:1ms
StringBuffer共耗时:1ms
    综上所述,我们发现通过+号进行拼接性能最差,concat其次,最佳是StringBuilder和StringBuffer,但是由于StringBuffer的线程安全,所以它的速度实际上是要比StringBuilder低的。
    最后说明一下各自的使用场景:
  1. 在进行循环拼接时最好不使用String(因为不管是+号还是concat性能都不算太好)
  2. 在不考虑线程安全时建议使用StringBuilder
    本文章基于JDK1.8,大家如果发现文章有任何问题,还请及时在文章中留言哈,大家一起学习!
作者:徐先森 文章名: 《关于Java中String-StringBuilder-StringBuffer的区别》
收录情况: 百度已收录
版权说明:若无特别注明,本文皆为 "徐博客”原创,转载请保留出处!

相关推荐

网友评论(已关闭)