Java系列之 - String、StringBuilder橘券、StringBuffer

String


public final class String implements Serializable, Comparable<String>, CharSequence {
    private final char[] value;
    ......
}

final修飾的String 類额湘,以及final修飾的char[] value,表示String類不可被繼承旁舰,且value只能被初始化一次锋华。這里的value變量其實就是存儲了String字符串中的所有字符。

String a = new String("aa"):代表在堆內(nèi)存中創(chuàng)建了一個字符串對象箭窜,變量a指向該對象毯焕,而該對象又指向常量池中的字符串常量aa。
String b = "bb":代表變量b直接指向常量池中的字符串常量bb磺樱,不會在堆內(nèi)存中創(chuàng)建對象纳猫。

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 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);
}

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類的substring方法竹捉,concat方法续担,replace方法,都是內(nèi)部重新生成一個String對象的活孩。這也就是為什么我們?nèi)绻捎肧tring對象頻繁的進行拼接物遇,截取,替換操作效率很低下的原因憾儒。

StringBuilder


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

StringBuilder類繼承AbstractStringBuilder抽象類询兴,其中StringBuilder的大部分方法都是直接調(diào)用的父類的實現(xiàn)。

public StringBuilder() {
    super(16);
}

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

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

可以看出StringBuilder的默認初始容量是16起趾,并且都調(diào)用了父類的構(gòu)造方法诗舰。

public StringBuilder append(String str) {
    super.append(str);
    return this;
}
    
public StringBuilder delete(int start, int end) {
    super.delete(start, end);
    return this;
}
    
public StringBuilder insert(int offset, String str) {
    super.insert(offset, str);
    return this;
}
    
public int indexOf(String str) {
    return super.indexOf(str);
}

public StringBuilder reverse() {
    super.reverse();
    return this;
}

再來看他的一些操作方法也都是調(diào)用了父類的實現(xiàn),接下來我們就看看父類的具體實現(xiàn)训裆。

abstract class AbstractStringBuilder implements Appendable, CharSequence {
 
    char[] value;
    int count;

    AbstractStringBuilder() {}

    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
}

char[] value沒有final修飾眶根,代表它是可以擴展的蜀铲。接下來我們重點看一下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;
}

首先判斷是否為null属百,null也是可以append進去的记劝。

private AbstractStringBuilder appendNull() {
    int c = count;
    ensureCapacityInternal(c + 4);
    final char[] value = this.value;
    value[c++] = 'n';
    value[c++] = 'u';
    value[c++] = 'l';
    value[c++] = 'l';
    count = c;
    return this;
}

接著判斷數(shù)組容量是否滿足此次append,不滿足的話執(zhí)行擴容:嘗試將新容量擴為大小變成2倍+2族扰,如果不夠厌丑,直接擴充到需要的容量大小,最大容量為MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8渔呵。

private void ensureCapacityInternal(int minimumCapacity) {
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value, newCapacity(minimumCapacity));
    }
}

private int newCapacity(int minCapacity) {
    //嘗試將新容量擴為大小變成2倍+2怒竿,如果不夠,直接擴充到需要的容量大小扩氢。
    int newCapacity = (value.length << 1) + 2;
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
}

private int hugeCapacity(int minCapacity) {
    if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
        throw new OutOfMemoryError();
    }
    return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
}

緊接著通過getChars調(diào)native方法getCharsNoCheck實現(xiàn)真正的append

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    //首先做一些參數(shù)校驗耕驰,這里就省略了
    ......
    getCharsNoCheck(srcBegin, srcEnd, dst, dstBegin);
}

@FastNative
native void getCharsNoCheck(int start, int end, char[] buffer, int index);

整個StringBuilder的append方法,本質(zhì)上是調(diào)用System的native方法录豺,直接將String 類型的str字符串中的字符數(shù)組朦肘,拷貝到了StringBuilder的字符數(shù)組中。

最后說下StringBuilder的toString方法:

@Override
public String toString() {
    if (count == 0) {
        return "";
    }
    return StringFactory.newStringFromChars(0, count, value);
}

這里的toString方法直接new 一個String對象巩检,將StringBuilder對象的value進行一個拷貝厚骗,重新生成一個對象示启,不共享之前StringBuilder的char[]兢哭。

以上就是StringBuilder的拼接字符串的原理分析,可以發(fā)現(xiàn)沒有像String一樣去重新new 對象夫嗓,所以在頻繁的拼接字符上迟螺,StringBuilder的效率遠遠高于String類。

StringBuffer


public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence {
    
    //transient關(guān)鍵字的作用:被修飾的成員屬性變量不被序列化
    private transient char[] toStringCache;
    static final long serialVersionUID = 3388685877147921107L;

    public StringBuffer() {
        super(16);
    }

    public StringBuffer(int capacity) {
        super(capacity);
    }

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

其構(gòu)造方法和成員基本和StringBuilder一樣舍咖,唯一區(qū)別就是char[]不允許序列化矩父。

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

其對應(yīng)的內(nèi)部方法都加了synchronized關(guān)鍵字,所以是線程安全的數(shù)據(jù)結(jié)構(gòu)排霉。

@Override
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, 0, count);
}

如果StringBuffer對象此時存在toStringCache窍株,在多次調(diào)用其toString方法時,其new出來的String對象是會共享同一個char[] 內(nèi)存的攻柠,達到共享的目的球订。但是StringBuffer只要做了修改,其toStringCache屬性值都會置null處理瑰钮。這也是StringBuffer和StringBuilder的一個區(qū)別點冒滩。

總結(jié)


String 類不可變,內(nèi)部維護的char[] 數(shù)組長度不可變浪谴,為final修飾开睡,String類也是final修飾因苹,不存在擴容。字符串拼接篇恒,截取扶檐,都會生成一個新的對象。頻繁操作字符串效率低下婚度,因為每次都會生成新的對象蘸秘。

StringBuilder 類內(nèi)部維護可變長度char[] , 初始化數(shù)組容量為16蝗茁,存在擴容醋虏, 其append拼接字符串方法內(nèi)部調(diào)用System的native方法,進行數(shù)組的拷貝哮翘,不會重新生成新的StringBuilder對象颈嚼。非線程安全的字符串操作類, 其每次調(diào)用 toString方法而重新生成的String對象饭寺,不會共享StringBuilder對象內(nèi)部的char[]阻课,會進行一次char[]的copy操作。

StringBuffer 類內(nèi)部維護可變長度char[]艰匙, 基本上與StringBuilder一致限煞,但其為線程安全的字符串操作類,大部分方法都采用了Synchronized關(guān)鍵字修改员凝,以此來實現(xiàn)在多線程下的操作字符串的安全性署驻。其toString方法而重新生成的String對象,會共享StringBuffer對象中的toStringCache屬性(char[])健霹,但是每次的StringBuffer對象修改旺上,都會置null該屬性值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載糖埋,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者宣吱。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瞳别,隨后出現(xiàn)的幾起案子征候,更是在濱河造成了極大的恐慌,老刑警劉巖祟敛,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疤坝,死亡現(xiàn)場離奇詭異,居然都是意外死亡垒棋,警方通過查閱死者的電腦和手機卒煞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叼架,“玉大人畔裕,你說我怎么就攤上這事衣撬。” “怎么了扮饶?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵具练,是天一觀的道長。 經(jīng)常有香客問我甜无,道長扛点,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任岂丘,我火速辦了婚禮陵究,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奥帘。我一直安慰自己铜邮,他們只是感情好,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布寨蹋。 她就那樣靜靜地躺著松蒜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪已旧。 梳的紋絲不亂的頭發(fā)上秸苗,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機與錄音运褪,去河邊找鬼惊楼。 笑死,一個胖子當著我的面吹牛吐句,可吹牛的內(nèi)容都是我干的胁后。 我是一名探鬼主播店读,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼嗦枢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了屯断?” 一聲冷哼從身側(cè)響起文虏,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎殖演,沒想到半個月后氧秘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡趴久,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年丸相,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彼棍。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡灭忠,死狀恐怖膳算,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弛作,我是刑警寧澤涕蜂,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站映琳,受9級特大地震影響机隙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜萨西,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一有鹿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谎脯,春花似錦印颤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至咸产,卻和暖如春矢否,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脑溢。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工僵朗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屑彻。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓验庙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親社牲。 傳聞我的和親對象是個殘疾皇子粪薛,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361