JavaSE源码分析(二):String、StringBuffer和StringBuilder的区别

前言

String、StringBuffer和StringBuilder是Java中关于字符串的三个常用类。它们之间的区别网上遍地都是,这里话不多说直接列出,然后我们再通过源码来更深入理解它们存在这些差异的原因是什么。

String、StringBuffer和StringBuilder的区别:

1、可变性问题:String是不可变字符序列,StringBuffer和StringBuilder是可变字符序列

2、安全性问题:String和StringBuffer是线程安全的,StringBuilder是线程不安全的

3、效率问题:通常情况下StringBuilder > StringBuffer > String

可变性问题

 🔷String

我们先看下String的源码,其中包括了两个String的操作方法substring()和replace()。

public final class String implements java.io.Serializable,Comparable<String>,CharSequence{
    /** The value is used for character storage. */
    private final char value[];

    ......

    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

    ......

    public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }

    ......
}

String类中字符数组value是用了final修饰的,给它赋值之后就无法改变。而从substring()和replace()方法也能看出,对String进行改变操作时,实际返回的是新new出来的String对象。所以说,String是不可变的。

🔷StringBuffer和StringBuilder

StringBuffer和StringBuilder的字符数组value都是继承于AbstractStringBuilder 抽象类,且重写了AbstractStringBuilder的append()方法拼接字符串。由于两者很相似,因此在说明它们的可变性问题时,就以StringBuffer的源码为例。


public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence {

    char value[]; //实际是继承了AbstractStringBuilder的value属性,为了对比方便放在这里

    ......

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

    ......

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

    ......
}

源码中,value属性并没有final修饰,说明是可以改变的。我们再看它拼接字符串的append()方法,调用了父类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()返回的还是对象本身(this),这说明了StringBuffer是可变的。

安全性问题

StringBuffer和StringBuilder的方法基本是一样的,区别是StringBuffer的大多数方法上都有synchoronized修饰,有同步锁机制保证线程安全,而StringBuilder并没有。因此StringBuffer是线程安全的,StringBuilder是线程不安全的。

public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence {

    ......

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

    ......
}

public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence {

    ......

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

    .......
}

至于String,由于它本身就是不可变的,线程对于堆中指定的一个String对象只能读取,无法修改。还有比这更安全的么😂......

效率问题

首先可以明确的是,由于StringBuffer为了达到线程安全的目的,其效率是比StringBuilder略低的。那么用“+”拼接String和StringBuilder的append()相比起来性能如何呢?

讨论这个话题之前我们需要搞清楚"+"是如何实现String的拼接的。我们看下面这个测试用例。

public class Test {
    public static void main(String[] args) {
        String a = "str" + "ing";
        String b = a + "builder";
        String c = a + b ;
        System.out.println(a);  // string
        System.out.println(b);  // stringbuilder
        System.out.println(c);  // stringstringbuilder
    }
}

我们通过反编译器(楼主用的是Jad)得到反编译后的java文件,看下"+"背后发生了什么。

public static void main(String args[]){
        String s = "string";
        String s1 = (new StringBuilder()).append(s).append("builder").toString();
        String s2 = (new StringBuilder()).append(s).append(s1).toString();
        System.out.println(s);
        System.out.println(s1);
        System.out.println(s2);
}

我们发现:

1、仅仅是字面值常量字符串之间的拼接,编译后就是拼接好的字符串常量

2、涉及到字符串变量的拼接时,“+”其实是通过新建StringBuilder对象并调用append()方法来进行拼接的

因此字面值常量字符串的拼接,在编译时已经拼接好了字符串,不会影响程序运行的性能。所以这种情况下的“+”拼接的性能毫无疑问是超过StringBuilder和StringBuffer的。

那么有字符串变量拼接时,既然"+"就相当于StringBuilder的append(),程序中使用它们的效率就相同吗?其实不尽然。我们看下面两种场景。

🔷使用频率较低的情况

public class Test {
    public static void main(String[] args) {

        String a = "abc";
        String b = a + "d";

        StringBuilder sb = new StringBuilder();
        String c = sb.append(a).append("d").toString();

    }
}

反编译后如下:

public static void main(String args[]){
        String s = "abc";
        String s1 = (new StringBuilder()).append(s).append("d").toString();
        StringBuilder stringbuilder = new StringBuilder();
        String s2 = stringbuilder.append(s).append("d").toString();
}

这种场景下,"+"和StringBuilder.append()的性能相差不大。

🔷使用频率很高的情况

我们通过for循环来模拟这种场景。

public class Test {
    public static void main(String[] args) {

        String a = "a";
        String b = "";
        for (int i = 0; i < 10000; i++) {
            b = b + a;
        }

        String c = "c";
        String d = "";
        StringBuilder sb = new StringBuilder();
        for (int j = 0; j < 10000; j++) {
            d = sb.append(d).append(c).toString();
        }

    }
}

反编译后如下:

    public static void main(String args[]){
        String s = "a";
        String s1 = "";
        for(int i = 0; i < 10000; i++)
            s1 = (new StringBuilder()).append(s1).append(s).toString();

        String s2 = "c";
        String s3 = "";
        StringBuilder stringbuilder = new StringBuilder();
        for(int j = 0; j < 10000; j++)
            s3 = stringbuilder.append(s3).append(s2).toString();

    }

这种情况下,每一行的“+”操作都会创建一个StringBuilder对象,而直接使用StringBuilder可以只创建一个对象进行复用。因此当使用的频率很多时,由于创建对象消耗了大量资源,所以"+"性能是远不如StringBuilder的。当然如果你不复用StringBuilder而采用下面代码的形式,那么两者就差不多了.......(你开心就好🙃)

public static void main(String[] args) {

    String c = "c";
    String d = "";
    for (int j = 0; j < 10000; j++) {
        StringBuilder sb = new StringBuilder();
        d = sb.append(d).append(c).toString();
    }

}

总结

读完本文,相信大家都已经清楚了这三者的区别以及其中的原理。这里再次总结一下。

String、StringBuffer和StringBuilder的区别:

1、可变性问题:String是不可变字符序列,StringBuffer和StringBuilder是可变字符序列

2、安全性问题:String和StringBuffer是线程安全的,StringBuilder是线程不安全的

3、效率问题:

(1)不考虑线程安全的情况下,StringBuilder > StringBuffer

(2)字面值常量字符串拼接时,String的"+" > StringBuilder/StringBuffer

(3)字符串变量拼接时,若拼接次数较少时,String的"+" ≈ StringBuilder/StringBuffer;若拼接次数很多时,String的"+" << StringBuilder/StringBuffer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值