String、StringBuffer、StringBuilder的理解

遛狗的程式設計師發表於2018-07-26

問題:

理解 Java的字串,String、StringBuffer、StringBuilder 有什麼區別?

知識點
  1. 字串設計和實現考量 String是Immutable(執行緒安全、字串常量池複用)。Immutable物件在拷貝時候不需要額外複製資料。至於為什麼imumutable,原始碼如下:
//關鍵點 final 
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    // The associated character storage is managed by the runtime. We only
    // keep track of the length here.
    //關鍵點final private
    // private final char value[];
    private final int count;
複製程式碼
  1. StringBuffer、StringBuilder底層都是利用可修改的陣列(JDK 9之後是byte)陣列,都繼承了AbstractStringBuilder,裡面包含了基本操作。區別StringBuffer增加了synchronized。相關原始碼如下:
//AbstractStringBuilder 沒有加final 也沒private修飾
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    char[] value;
    int count;
    private static final int MAX_ARRAY_SIZE = 2147483639;

    AbstractStringBuilder() {
    }
複製程式碼
//StringBuilder擷取部分原始碼
static final long serialVersionUID = 4383685877147921099L;

    public StringBuilder() {
        super(16);
    }

    public StringBuilder(int var1) {
        super(var1);
    }

    public StringBuilder(String var1) {
        super(var1.length() + 16);
        this.append(var1);
    }

    public StringBuilder(CharSequence var1) {
        this(var1.length() + 16);
        this.append(var1);
    }

    public StringBuilder append(Object var1) {
        return this.append(String.valueOf(var1));
    }

    public StringBuilder append(String var1) {
        super.append(var1);
        return this;
    }

    public StringBuilder append(StringBuffer var1) {
        super.append(var1);
        return this;
    }

    public StringBuilder append(CharSequence var1) {
        super.append(var1);
        return this;
    }

    public StringBuilder append(CharSequence var1, int var2, int var3) {
        super.append(var1, var2, var3);
        return this;
    }
複製程式碼
//StringBuffer部分原始碼 多了synchronized
public StringBuffer(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

    public synchronized int length() {
        return count;
    }

    public synchronized int capacity() {
        return value.length;
    }


    public synchronized void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > value.length) {
            expandCapacity(minimumCapacity);
        }
    }
複製程式碼
  1. 字串快取 String在Java 6 以後提供了intern()方法,目的是提示JVM把相應字串快取起來,以便重複使用。

注意:Java 6這種歷史版本,並不推薦大量使用intern(),因為快取的字串是存在“永久代”中,這個空間比較有限。也基本不會被FULLGC之外的垃圾收集照顧到。所以,使用不當,容易OOM。後續版本中,被放到堆中,設定永久代在Java 8中被後設資料區替代了。

intern()感興趣可以參考文章:Java提高篇——理解String 及 String.intern() 在實際中的應用

  1. String自身也有相關優化 有興趣可以自己檢視相關文章,主要是char陣列換成了byte陣列加上一個標誌編碼所謂的coder。
回答問題:
String的建立機理

由於String在Java世界中使用過於頻繁,Java為了避免在一個系統中使用大量的Java物件,引入了字串常量池的概念。其執行機制是:建立一個字串的時候,首先檢查池中是否有相等的字串物件,如果有就不需要建立,直接從池中找到物件引用,如果沒有的話,新建字串物件,返回物件引用。但是,通過new方法建立的不會檢查常量池是否存在,而是直接在堆中或者棧中建立一個新的物件,也不會把物件放入池中。上面所說的只適用於直接給String引用賦值的情況。

注意:String是immutable Strng提供了inter()方法可以將Strng物件新增到池中,並且返回該物件的引用。(如果由equals()確定池中有該字串,那就直接返回)。

當兩個String物件擁有相等的值的時候,他們只引用字串中同一個拷貝。當同一個字串大量出現的時候,可以大量節省記憶體空間。

StringBuffer/StringBuilder

StringBuffer/StringBuilder相同點:

  • String/StringBuilder相對於String的話,他們值是可以改變的,並且值改變後,他們引用不會變。他們在構造的時候使用一個預設的陣列,在後續加入資料後超過預設大小後會建立更大的陣列,並且將原來陣列的內容複製過來,再丟棄舊的陣列。因此,專案開發的時候,對於較大物件的擴容會效能,因此,能預估大小,最好不過。

StringBuffer/StringBuilder不同點:

  • 另外StringBuffer是執行緒安全(方法定義前面使用了synchronize),StringBuilder不是。StringBuffer效能要低於StringBuilder。
應用場景:

String 適用於常量宣告。如:

 public static final int DEVICE_NOT_AVAILABLE_ERROR_CODE = 390004;// 裝置未啟用或者被鎖住
複製程式碼

StringBuffer,適用於頻繁進行字串運算(如拼接、替換、刪除等),並且執行在多執行緒環境下,建議使用,比如Http引數解析和封裝。

/**
     * 獲取POST請求的base url
     *
     * @param requestHolder
     * @return
     * @throws AlipayApiException
     */
    private String getRequestUrl(RequestParametersHolder requestHolder) throws AlipayApiException {
        StringBuffer urlSb = new StringBuffer(serverUrl);
        try {
            String sysMustQuery = WebUtils.buildQuery(requestHolder.getProtocalMustParams(),
                    charset);
            String sysOptQuery = WebUtils.buildQuery(requestHolder.getProtocalOptParams(), charset);

            urlSb.append("?");
            urlSb.append(sysMustQuery);
            if (sysOptQuery != null & sysOptQuery.length() > 0) {
                urlSb.append("&");
                urlSb.append(sysOptQuery);
            }
        } catch (IOException e) {
            throw new AlipayApiException(e);
        }

        return urlSb.toString();
    }
複製程式碼

StringBuilder,適用於頻繁進行字串運算(如拼接、替換、刪除等),並且執行在單執行緒環境下,建議使用,比如Json的封裝。

/**
         * Extracts absolute path form a given URI. E.g., passing
         * <code>http://google.com:80/execute?query=cat#top</code>
         * will result in <code>/execute?query=cat#top</code>.
         *
         * @param uri URI which absolute path has to be extracted,
         * @return the absolute path of the URI,
         */
        private String getAbsolutePathFromAbsoluteURI(URI uri) {
            String rawPath = uri.getRawPath();
            String rawQuery = uri.getRawQuery();
            String rawFragment = uri.getRawFragment();
            StringBuilder absolutePath = new StringBuilder();

            if (rawPath != null) {
                absolutePath.append(rawPath);
            } else {
                absolutePath.append("/");
            }
            if (rawQuery != null) {
                absolutePath.append("?").append(rawQuery);
            }
            if (rawFragment != null) {
                absolutePath.append("#").append(rawFragment);
            }
            return absolutePath.toString();
        }

複製程式碼

參考:

宣告:此為原創,轉載請聯絡作者


作者:微信公眾號新增公眾號-遛狗的程式設計師 ,或者可以掃描以下二維碼關注相關技術文章。

qrcode_for_gh_1ba0785324d6_430.jpg
當然喜愛技術,樂於分享的你也可以可以新增作者微訊號:

WXCD.jpeg

相關文章