老生常談 String、StringBuilder、StringBuffer

[TOC]

字符串就是一連串的字符序列,Java提供了String、StringBuilder、StringBuffer三個(gè)類來封裝字符串

String

String類是不可變類,String對象被創(chuàng)建以后,對象中的字符序列是不可改變的,直到這個(gè)對象被銷毀

為什么是不可變的

jdk1.8
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    //jdk1.9中將char數(shù)組替換為byte數(shù)組,緊湊字符串帶來的優(yōu)勢:更小的內(nèi)存占用,更快的操作速度。
    //構(gòu)造函數(shù)
     public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
    //構(gòu)造函數(shù)
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    //返回一個(gè)新的char[]
    public char[] toCharArray() {
        // Cannot use Arrays.copyOf because of class initialization order issues
        char result[] = new char[value.length];
        System.arraycopy(value, 0, result, 0, value.length);
        return result;
    }
 }

根據(jù)上面的代碼,我們看看String究竟是怎么保證不可變的。

  • String類被final修飾,不可被繼承
  • string內(nèi)部所有成員都設(shè)置為私有變量,外部無法訪問
  • 沒有向外暴露修改value的接口
  • value被final修飾,所以變量的引用不可變。
  • char[]·為引用類型仍可以通過引用修改實(shí)例對象,為此String(char value[])構(gòu)造函數(shù)內(nèi)部使用的copyOf而不是直接將value[]復(fù)制給內(nèi)部變量`。
  • 在獲取value時(shí),并沒有將value的引用直接返回,而是采用了arraycopy()的方式返回一個(gè)新的char[]
  • String類中的函數(shù)也處處透露著不可變的味道,比如:replace()
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) {
                //重新創(chuàng)建新的char[],不改變原有對象中的值
                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++;
                }
                //最后返回新創(chuàng)建的String對象
                return new String(buf, true);
            }
        }
        return this;
    }

當(dāng)然不可變也不是絕對的,還是可以通過反射獲取到變value引用,然后通過value[]修改數(shù)組的方式改變value對象實(shí)例

        String a = "Hello World!";
        String b = new String("Hello World!");
        String c = "Hello World!";

       //通過反射修改字符串引用的value數(shù)組
        Field field = a.getClass().getDeclaredField("value");
        field.setAccessible(true);
        char[] value = (char[]) field.get(a);
        System.out.println(value);//Hello World!
        value[5] = '&';
        System.out.println(value);//Hello&World!

        // 驗(yàn)證b、c是否被改變
        System.out.println(b);//Hello&World! 
        System.out.println(c);//Hello&World!

寫到這里該如何引出不可變的好處呢?忘記反射吧,我們聊聊不可變的好處吧

不可變的優(yōu)點(diǎn)

保證了線程安全

同一個(gè)字符串實(shí)例可以被多個(gè)線程共享。

保證了基本的信息安全

比如,網(wǎng)絡(luò)通信的IP地址,類加載器會根據(jù)一個(gè)類的完全限定名來讀取此類諸如此類,不可變性提供了安全性。

字符串緩存(常量池)的需要

具統(tǒng)計(jì),常見應(yīng)用使用的字符串中有大約一半是重復(fù)的,為了避免創(chuàng)建重復(fù)字符串,降低內(nèi)存消耗和對象創(chuàng)建時(shí)的開銷。JVM提供了字符串緩存的功能——字符串常量池。如果字符串是可變的,我們就可以通過引用改變常量池總的同一個(gè)內(nèi)存空間的值,其他指向此空間的引用也會發(fā)生改變。

支持hash映射和緩存。

因?yàn)樽址遣豢勺兊?,所以在它?chuàng)建的時(shí)候hashcode就被緩存了,不需要重新計(jì)算。這就使得字符串很適合作為Map中的鍵,字符串的處理速度要快過其它的鍵對象。這就是HashMap中的鍵往往都使用字符串。

不可變的缺點(diǎn)

由于它的不可變性,像字符串拼接、裁剪等普遍性的操作,往往對應(yīng)用性能有明顯影響。

為了解決這個(gè)問題,java為我們提供了兩種解決方案

  • 字符串常量池
  • StringBuilder、StringBuffer是可變的

字符串常量池

還是剛才反射的示例

        String a = "Hello World!";
        String b = new String("Hello World!");
        String c = "Hello World!";
        //判斷字符串變量是否指向同一塊內(nèi)存
        System.out.println(a == b);
        System.out.println(a == c);
        System.out.println(b == c);

        // 通過反射觀察a, b, c 三者中變量value數(shù)組的真實(shí)位置
        Field a_field = a.getClass().getDeclaredField("value");
        a_field.setAccessible(true);
        System.out.println(a_field.get(a));

        Field b_field = b.getClass().getDeclaredField("value");
        b_field.setAccessible(true);
        System.out.println(b_field.get(b));

        Field c_field = c.getClass().getDeclaredField("value");
        c_field.setAccessible(true);
        System.out.println(c_field.get(c));
        //通過反射發(fā)現(xiàn)String對象中變量value指向了同一塊內(nèi)存

輸出

false
true
false
[C@6f94fa3e
[C@6f94fa3e
[C@6f94fa3e

字符串常量的創(chuàng)建過程:

  1. 判斷常量池中是否存在"Hello World!"常量,如果有直接返回該常量在池中的引用地址
  2. 如果沒有,先創(chuàng)建一個(gè)char["Hello World!".length()]數(shù)組對象,然后在常量池中創(chuàng)建一個(gè)字符串對象并用數(shù)組對象初始化字符串對象的成員變量value,然后將這個(gè)字符串的引用返回,比如賦值給a

由此可見,a和c對象指向常量池中相同的內(nèi)存空間不言自明。

而b對象的創(chuàng)建是建立在以上的創(chuàng)建過程的基礎(chǔ)之上的。
"Hello World!"常量創(chuàng)建完成時(shí)返回的引用,會經(jīng)過String的構(gòu)造函數(shù)。

    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

構(gòu)造函數(shù)內(nèi)部將引用的對象成員變量value賦值給了內(nèi)部成員變量value,然后將新創(chuàng)建的字符創(chuàng)對象引用賦值給了b,這個(gè)過程發(fā)生在堆中。

再來感受下下面這兩行代碼有什么區(qū)別

  String b = new String(a);
  String b = new String("Hello World!");

StringBuilder和StringBuffer

二者都是可變的

為了彌補(bǔ)String的缺陷,Java先后提供了StringBuffer和StringBuilder可變字符串類。

二者都繼承至AbstractStringBuilder,AbstractStringBuilder使用了char[] value字符數(shù)組

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
}

可以看出AbstractStringBuilder類和其成員變量value都沒有使用final關(guān)鍵字。

value數(shù)組的默認(rèn)長度

StringBuilder和StringBuffer的value數(shù)組默認(rèn)初始長度是16

    public StringBuilder() {
        super(16);
    }
    public StringBuffer() {
        super(16);
    }

如果我們拼接的字符串長度大概是可以預(yù)計(jì)的,那么最好指定合適的capacity,避免多次擴(kuò)容的開銷。

擴(kuò)容產(chǎn)生多重開銷:拋棄原有數(shù)組,創(chuàng)建新的數(shù)組,進(jìn)行arrycopy。

二者的區(qū)別

StringBuilder是非線程安全的,StringBuffer是線程安全的。

StringBuffer類中的方法使用了synchronized同步鎖來保證線程安全。
關(guān)于鎖的話題非常大,會單獨(dú)成文來說明,這里推薦一篇不錯(cuò)的博客,有興趣的可以看看

JVM源碼分析之synchronized實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容