從源碼角度徹底搞懂String、StringBuffer孩擂、StringBuilder

遇到好文隨手轉(zhuǎn)發(fā)~原文地址:https://blog.csdn.net/xfhy_/article/details/80019618

一狼渊、引言

學(xué)Java很久了,一直處于使用API+查API的狀態(tài),不了解原理,久而久之總是覺(jué)得很虛,作為一名合格的程序員這是不允許的,不能一直當(dāng)API Player,我們要去了解分析底層實(shí)現(xiàn),下次在使用時(shí)才能知己知彼.知道在什么時(shí)候該用什么方法和什么類比較合適.

image

在之前,我知道的關(guān)于String,StringBuffer,StringBuilder的知識(shí)點(diǎn)大概如下

  1. String是不可變的(修改String時(shí),不會(huì)在原有的內(nèi)存地址修改类垦,而是重新指向一個(gè)新對(duì)象)狈邑,String用final修飾,不可繼承蚤认,String本質(zhì)上是個(gè)final的char[]數(shù)組官地,所以char[]數(shù)組的內(nèi)存地址不會(huì)被修改,而且String 也沒(méi)有對(duì)外暴露修改char[]數(shù)組的方法.不可變性可以保證線程安全以及字符串串常量池的實(shí)現(xiàn).頻繁的增刪操作是不建議使用String的.
  2. StringBuffer是線程安全的,多線程建議使用這個(gè).
  3. StringBuilder是非線程安全的,單線程使用這個(gè)更快.

對(duì)于上面這些結(jié)論,我也不知道從哪里來(lái)的,,,,感覺(jué)好像是前輩的經(jīng)驗(yàn)吧,,,好了,廢話不多說(shuō),直接上代碼吧.

image

二烙懦、String源碼分析

看下繼承結(jié)構(gòu)源碼:

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

可以看到String是final的,不允許繼承.里面用來(lái)存儲(chǔ)value的是一個(gè)final數(shù)組,也是不允許修改的.String有很多方法,下面就String類常用方法進(jìn)行分析.

1.構(gòu)造方法

public String() {
    this.value = "".value;
}
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}
public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}
...

可以看到默認(rèn)的構(gòu)造器是構(gòu)建的空字符串,其實(shí)所有的構(gòu)造器就是給value數(shù)組賦初值.

2.字符串長(zhǎng)度

返回該字符串的長(zhǎng)度,這太簡(jiǎn)單了,就是返回value數(shù)組的長(zhǎng)度.

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

3.字符串某一位置字符

返回字符串中指定位置的字符;

public char charAt(int index) {
    //1\. 首先判斷是否越界
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    //2\. 返回相應(yīng)位置的值
    return value[index];
}

4.提取子串

用String類的substring方法可以提取字符串中的子串赤炒,簡(jiǎn)單分析一下substring(int beginIndex, int endIndex)吧,該方法從beginIndex位置起氯析,從當(dāng)前字符串中取出到endIndex-1位置的字符作為一個(gè) 新的字符串(重新new了一個(gè)String) 返回.

方法內(nèi)部是將數(shù)組進(jìn)行部分復(fù)制完成的,所以該方法不會(huì)對(duì)原有的數(shù)組進(jìn)行更改.

public String substring(int beginIndex, int endIndex) {
    //1.驗(yàn)證入?yún)⑹欠裨浇?    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    //2\. 記錄切割長(zhǎng)度
    int subLen = endIndex - beginIndex;
    //3\. 入?yún)⒑戏ㄐ?    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    //4\. 如果開(kāi)始切割處是0,結(jié)束切割處是value數(shù)組長(zhǎng)度,那么相當(dāng)于沒(méi)有切割嘛,就直接返回原字符串;如果是其他情況:則重新新建一個(gè)String對(duì)象
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}

/**
* 通過(guò)一個(gè)char數(shù)組復(fù)制部分內(nèi)容生成一個(gè)新數(shù)組,復(fù)制區(qū)間:從offset到offset+count處.
*/
public String(char value[], int offset, int count) {
    if (offset < 0) {
        throw new StringIndexOutOfBoundsException(offset);
    }
    if (count <= 0) {
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        //count==0
        if (offset <= value.length) {
            this.value = "".value;
            return;
        }
    }
    // Note: offset or count might be near -1>>>1.
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }
    //復(fù)制一部分
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}

5.字符串比較

  • compareTo(String anotherString)

    該方法是對(duì)字符串內(nèi)容按字典順序進(jìn)行大小比較,通過(guò)返回的整數(shù)值指明當(dāng)前字符串與參數(shù)字符串的大小關(guān)系.若當(dāng)前對(duì)象比參數(shù)大則返回正整數(shù)莺褒,反之返回負(fù)整數(shù)掩缓,相等返回0.

    主要是挨個(gè)字符進(jìn)行比較

public int compareTo(String anotherString) {
    //1\. 記錄長(zhǎng)度
    int len1 = value.length;
    int len2 = anotherString.value.length;
    //2\. 最短長(zhǎng)度
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    //3\. 循環(huán)逐個(gè)字符進(jìn)行比較,如果不相等則返回字符之差  
    //這里只需要循環(huán)lim次就行了
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            //可能是正數(shù)或負(fù)數(shù)
            return c1 - c2; 
        }
        k++;
    }
    //4\. 最后返回長(zhǎng)度之差    這里可能是0,即相等
    return len1 - len2;
}
  • compareToIgnore(String anotherString)

    與compareTo()方法相似,但忽略大小寫(xiě).

    實(shí)現(xiàn):從下面的源碼可以看出,最終實(shí)現(xiàn)是通過(guò)一個(gè)內(nèi)部類CaseInsensitiveComparator,它實(shí)現(xiàn)了Comparator和Serializable接口,并實(shí)現(xiàn)了compare()方法,里面的實(shí)現(xiàn)方法和上面的compareTo()方法差不多,只不過(guò)忽略大小寫(xiě).

public int compareToIgnoreCase(String str) {
    return CASE_INSENSITIVE_ORDER.compare(this, str);
}
public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                        = new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
        implements Comparator<String>, java.io.Serializable {
    // use serialVersionUID from JDK 1.2.2 for interoperability
    private static final long serialVersionUID = 8575799808933029326L;

    public int compare(String s1, String s2) {
        int n1 = s1.length();
        int n2 = s2.length();
        int min = Math.min(n1, n2);
        for (int i = 0; i < min; i++) {
            char c1 = s1.charAt(i);
            char c2 = s2.charAt(i);
            if (c1 != c2) {
                c1 = Character.toUpperCase(c1);
                c2 = Character.toUpperCase(c2);
                if (c1 != c2) {
                    c1 = Character.toLowerCase(c1);
                    c2 = Character.toLowerCase(c2);
                    if (c1 != c2) {
                        // No overflow because of numeric promotion
                        return c1 - c2;
                    }
                }
            }
        }
        return n1 - n2;
    }

    /** Replaces the de-serialized object. */
    private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
  • equals(Object anotherObject)

    比較當(dāng)前字符串和參數(shù)字符串遵岩,在兩個(gè)字符串相等的時(shí)候返回true你辣,否則返回false.

    大體實(shí)現(xiàn)思路:

    1. 先判斷引用是否相同
    2. 再判斷該Object對(duì)象是否是String的實(shí)例
    3. 再判斷兩個(gè)字符串的長(zhǎng)度是否一致
    4. 最后挨個(gè)字符進(jìn)行比較
public boolean equals(Object anObject) {
    //1\. 引用相同  
    if (this == anObject) {
        return true;
    }

    //2\. 是String的實(shí)例?
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        //3\. 長(zhǎng)度
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            //4\. 挨個(gè)字符進(jìn)行比較
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
  • equalsIgnoreCase(String anotherString)

    與equals方法相似巡通,但忽略大小寫(xiě).但是這里要稍微復(fù)雜一點(diǎn),因?yàn)闋窟B到另一個(gè)方法regionMatches(),沒(méi)關(guān)系,下面跟著我一起慢慢分析.

    在equalsIgnoreCase()方法里面首先是校驗(yàn)引用值是否一致,再判斷否為空,緊接著判斷長(zhǎng)度是否一致,最后通過(guò)regionMatches()方法測(cè)試兩個(gè)字符串每個(gè)字符是否相等(忽略大小寫(xiě)).

    在regionMatches()方法中其實(shí)還是比較簡(jiǎn)單的,就是逐字符進(jìn)行比較,當(dāng)需要進(jìn)行忽略大小寫(xiě)時(shí),如果遇到不相等的2字符,先統(tǒng)一轉(zhuǎn)成大寫(xiě)進(jìn)行比較,如果相同則繼續(xù)比較下一個(gè),不相同則轉(zhuǎn)成小寫(xiě)再判斷是否一致.

public boolean equalsIgnoreCase(String anotherString) {
    return (this == anotherString) ? true
            : (anotherString != null)
            && (anotherString.value.length == value.length)
            && regionMatches(true, 0, anotherString, 0, value.length);
}
public boolean regionMatches(boolean ignoreCase, int toffset,
            String other, int ooffset, int len) {
        char ta[] = value;
        int to = toffset;
        char pa[] = other.value;
        int po = ooffset;
        // Note: toffset, ooffset, or len might be near -1>>>1.
        if ((ooffset < 0) || (toffset < 0)
                || (toffset > (long)value.length - len)
                || (ooffset > (long)other.value.length - len)) {
            return false;
        }
        while (len-- > 0) {
            //循環(huán)校驗(yàn)每個(gè)字符是否相等,相等則繼續(xù)校驗(yàn)下一個(gè)字符
            char c1 = ta[to++];
            char c2 = pa[po++];
            if (c1 == c2) {
                continue;
            }

            //如果遇到不相等的2字符,再判斷是否忽略大小寫(xiě).
            //先統(tǒng)一轉(zhuǎn)成大寫(xiě)進(jìn)行比較,如果相同則繼續(xù)比較下一個(gè),不相同則轉(zhuǎn)成小寫(xiě)再判斷是否一致
            if (ignoreCase) {
                // If characters don't match but case may be ignored,
                // try converting both characters to uppercase.
                // If the results match, then the comparison scan should
                // continue.
                char u1 = Character.toUpperCase(c1);
                char u2 = Character.toUpperCase(c2);
                if (u1 == u2) {
                    continue;
                }
                // Unfortunately, conversion to uppercase does not work properly
                // for the Georgian alphabet, which has strange rules about case
                // conversion.  So we need to make one last check before
                // exiting.
                if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
                    continue;
                }
            }
            return false;
        }
        return true;
    }

6.字符串連接

將指定字符串聯(lián)到此字符串的結(jié)尾,效果等價(jià)于”+”.

實(shí)現(xiàn)思路:構(gòu)建一個(gè)新數(shù)組,先將原來(lái)的數(shù)組復(fù)制進(jìn)新數(shù)組里面,再將需要連接的字符串復(fù)制進(jìn)新數(shù)組里面(存放到后面).

public String concat(String str) {
    //1\. 首先獲取傳入字符串長(zhǎng)度  咦,居然沒(méi)有對(duì)入?yún)⒑戏ㄐ赃M(jìn)行判斷?萬(wàn)一是null呢
    int otherLen = str.length();
    //2\. 如果傳入字符串長(zhǎng)度為0,就沒(méi)必要往后面走了
    if (otherLen == 0) {
        return this;
    }
    //3\. 記錄當(dāng)前數(shù)組長(zhǎng)度
    int len = value.length;
    //4\. 搞一個(gè)新數(shù)組(空間大小為len + otherLen),前面len個(gè)空間用來(lái)存放value數(shù)組
    char buf[] = Arrays.copyOf(value, len + otherLen);
    //5\. 將str存入buf數(shù)組的后面otherLen個(gè)空間里面
    str.getChars(buf, len);
    //6\. new一個(gè)String將新建的buf數(shù)組傳入
    return new String(buf, true);
}

//將des數(shù)組復(fù)制進(jìn)value數(shù)組中,dstBegin:目的數(shù)組放置的起始位置
void getChars(char dst[], int dstBegin) {
    System.arraycopy(value, 0, dst, dstBegin, value.length);
}

//這里的share參數(shù)貌似總是為true   所以是暫時(shí)沒(méi)用咯??
String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}

7.字符串中單個(gè)字符查找

  • indexOf(int ch/String str)

    返回指定字符在此字符串中第一次出現(xiàn)處的索引,在該對(duì)象表示的字符序列中第一次出現(xiàn)該字符的索引舍哄,如果未出現(xiàn)該字符宴凉,則返回 -1。

    其實(shí)該方法最后是調(diào)用的indexOf(ch/str, 0); 該方法放到下面進(jìn)行分析.

  • indexOf(int ch/String str, int fromIndex)

    該方法與第一種類似表悬,區(qū)別在于該方法從fromIndex位置向后查找.

    先分析indexOf(int ch, int fromIndex),該方法是查找ch在fromIndex索引之后第一次出現(xiàn)的索引.主要就是逐個(gè)字符進(jìn)行比較,相同則返回索引.如果未找到則返回-1.

public int indexOf(int ch, int fromIndex) {
    final int max = value.length;
    //1\. 邊界
    if (fromIndex < 0) {
        fromIndex = 0;
    } else if (fromIndex >= max) {
        // Note: fromIndex might be near -1>>>1.
        return -1;
    }

    //2\. 是否是罕見(jiàn)字符
    if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
        // handle most cases here (ch is a BMP code point or a
        // negative value (invalid code point))
        //在這里其實(shí)已經(jīng)處理了大多數(shù)情況
        final char[] value = this.value;
        //3\. 從fromIndex開(kāi)始,循環(huán),找到第一個(gè)與ch相等的進(jìn)行返回
        for (int i = fromIndex; i < max; i++) {
            if (value[i] == ch) {
                return i;
            }
        }
        return -1;
    } else {
        //4\. 罕見(jiàn)字符處理
        return indexOfSupplementary(ch, fromIndex);
    }
}

再來(lái)分析indexOf(String str, int fromIndex),該方法功能是從指定的索引處開(kāi)始弥锄,返回第一次出現(xiàn)的指定子字符串在此字符串中的索引.

大體思路:

  1. 有點(diǎn)類似于字符串查找子串,先在當(dāng)前字符串中找到與目標(biāo)字符串的第一個(gè)字符相同的索引處

  2. 再?gòu)拇怂饕霭l(fā)循環(huán)遍歷目標(biāo)字符串后面的字符.

  3. 如果全部相同,則返回下標(biāo);如果不全部相同,則重復(fù)步驟1

    文字可能描述不清楚,上圖片

    image

    我們要在beautifulauful中查找ful,那么步驟是首先找到f,再匹配后面的ul部分,找到則返回索引,未找到則繼續(xù)查找.

public int indexOf(String str, int fromIndex) {
    return indexOf(value, 0, value.length,
            str.value, 0, str.value.length, fromIndex);
}

//在source數(shù)組中查找target數(shù)組
static int indexOf(char[] source, int sourceOffset, int sourceCount,
        char[] target, int targetOffset, int targetCount,
        int fromIndex) {
    //1\. 校驗(yàn)參數(shù)合法性
    if (fromIndex >= sourceCount) {
        return (targetCount == 0 ? sourceCount : -1);
    }
    if (fromIndex < 0) {
        fromIndex = 0;
    }
    if (targetCount == 0) {
        return fromIndex;
    }

    //2\. 記錄第一個(gè)需要匹配的字符
    char first = target[targetOffset];
    //3\. 這一次匹配的能到達(dá)的最大索引
    int max = sourceOffset + (sourceCount - targetCount);

    //4\. 循環(huán)遍歷后面的數(shù)組
    for (int i = sourceOffset + fromIndex; i <= max; i++) {
        /* Look for first character. */
        //5\. 循環(huán)查找,直到查找到第一個(gè)和目標(biāo)字符串第一個(gè)字符相同的索引
        if (source[i] != first) {
            while (++i <= max && source[i] != first);
        }

        /* Found first character, now look at the rest of v2 */
        //6\. 找到了第一個(gè)字符,再來(lái)看看目標(biāo)字符串剩下的部分
        if (i <= max) {
            int j = i + 1;
            int end = j + targetCount - 1;
            //7\. 匹配一下目標(biāo)字符串后面的字符串是否相等  不相等的時(shí)候就跳出循環(huán)
            for (int k = targetOffset + 1; j < end && source[j]
                    == target[k]; j++, k++);
            //8\. 如果全部相等,則返回索引
            if (j == end) {
                /* Found whole string. */
                return i - sourceOffset;
            }
        }
    }
    return -1;
}

  • lastIndexOf(int ch/String str)

    該方法與第一種類似,區(qū)別在于該方法從字符串的末尾位置向前查找.

    實(shí)現(xiàn)方法也與第一種是類似的,只不過(guò)是從后往前查找.

public int lastIndexOf(int ch) {
    return lastIndexOf(ch, value.length - 1);
}
public int lastIndexOf(int ch, int fromIndex) {
    //1\. 判斷是否是罕見(jiàn)字符
    if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
        // handle most cases here (ch is a BMP code point or a
        // negative value (invalid code point))
        final char[] value = this.value;
        //2\. 從fromIndex(從哪個(gè)索引開(kāi)始), value.length - 1(數(shù)組最后一個(gè)索引)中小一點(diǎn)的往前找,這里之所以這樣做是因?yàn)閒romIndex可能比value.length-1大.這里求最小值就可以覆蓋所有情況,不管fromIndex和value.length-1誰(shuí)大.
        int i = Math.min(fromIndex, value.length - 1);
        //3\. 循環(huán) 逐個(gè)字符進(jìn)行比較,找到則返回索引
        for (; i >= 0; i--) {
            if (value[i] == ch) {
                return i;
            }
        }
        return -1;
    } else {
      //4\. 罕見(jiàn)字符處理
        return lastIndexOfSupplementary(ch, fromIndex);
    }
}
  • lastIndexOf(int ch/String str, int fromIndex)

    該方法與第二種方法類似蟆沫,區(qū)別在于該方法從fromIndex位置向前查找.

    實(shí)現(xiàn)思路:這里要稍微復(fù)雜一點(diǎn),相當(dāng)于從后往前查找指定子串.上圖吧

    image

    圖畫(huà)的有點(diǎn)丑,哈哈. 假設(shè)我們需要在StringBuffer中查找ABuff中的子串Buff,因?yàn)?code>Buff的長(zhǎng)度是4,所以我們最大的索引可能值是圖中的rightIndex.然后我們就開(kāi)始在source數(shù)組中匹配目標(biāo)字符串的最后一個(gè)字符,匹配到后,再逐個(gè)字符進(jìn)行比較剩余的字符,如果全部匹配,則返回索引.未全部匹配,則再次在source數(shù)組中尋找與目標(biāo)字符串最后一個(gè)字符相等的字符,然后找到后繼續(xù)匹配除去最后一個(gè)字符剩余的字符串. 唉~敘述的不是特別清晰,看代碼吧,代碼比我說(shuō)的清晰..

public int lastIndexOf(String str, int fromIndex) {
    return lastIndexOf(value, 0, value.length,
            str.value, 0, str.value.length, fromIndex);
}

static int lastIndexOf(char[] source, int sourceOffset, int sourceCount,
        char[] target, int targetOffset, int targetCount,
        int fromIndex) {
    /*
     * Check arguments; return immediately where possible. For
     * consistency, don't check for null str.
     */
     //1\. 最大索引的可能值
    int rightIndex = sourceCount - targetCount;
    //2\. 參數(shù)合法性檢驗(yàn)
    if (fromIndex < 0) {
        return -1;
    }
    if (fromIndex > rightIndex) {
        fromIndex = rightIndex;
    }
    /* Empty string always matches. */
    if (targetCount == 0) {
        return fromIndex;
    }

    //3\. 記錄目標(biāo)字符串最后一個(gè)字符索引處和該字符內(nèi)容
    int strLastIndex = targetOffset + targetCount - 1;
    char strLastChar = target[strLastIndex];
    //4\. 只需要遍歷到min處即可停止遍歷了,因?yàn)樵趍in前面的字符數(shù)量已經(jīng)小于目標(biāo)字符串的長(zhǎng)度了
    int min = sourceOffset + targetCount - 1;
    //5\. strLastChar在source中的最大索引
    int i = min + fromIndex;

//這里的語(yǔ)法不是很常見(jiàn),有點(diǎn)類似于goto,平時(shí)我們?cè)谑褂脮r(shí)盡量不采用這種方式,這種方式容易降低代碼的可讀性,而且容易出錯(cuò).
startSearchForLastChar:   
    while (true) {
        //6\. 在有效遍歷區(qū)間內(nèi),循環(huán)查找第一個(gè)與目標(biāo)字符串最后一個(gè)字符相等的字符,如果找到,則跳出循環(huán),該字符的索引是i
        while (i >= min && source[i] != strLastChar) {
            i--;
        }
        //7\. 如果已經(jīng)小于min了,那么說(shuō)明沒(méi)找到,直接返回-1
        if (i < min) {
            return -1;
        }
        //8\. 找到了,則再進(jìn)行查找目標(biāo)字符串除去最后一個(gè)字符剩下的子串
        //從最后一個(gè)字符的前一個(gè)字符開(kāi)始查找
        int j = i - 1;
        //9\. 目標(biāo)字符串除去最后一個(gè)字符剩下的子串長(zhǎng)度是targetCount - 1,此處start是此次剩余子串查找能到達(dá)的最小索引處
        int start = j - (targetCount - 1);
        //10\. 記錄目標(biāo)字符串的倒數(shù)第二個(gè)字符所在target中的索引
        int k = strLastIndex - 1;

        //11\. 循環(huán)查找剩余子串是否全部字符相同
        //不相同則直接跳出繼續(xù)第6步
        //全部相同則返回索引
        while (j > start) {
            if (source[j--] != target[k--]) {
                i--;
                continue startSearchForLastChar;
            }
        }
        return start - sourceOffset + 1;
    }
}

8.字符串中字符的替換

  • replace(char oldChar, char newChar)

    功能:用字符newChar替換當(dāng)前字符串中所有的oldChar字符籽暇,并返回一個(gè)新的字符串.
    大體思路:

    1. 首先判斷oldChar與newChar是否相同,相同的話就沒(méi)必要進(jìn)行后面的操作了
    2. 從最前面開(kāi)始匹配與oldChar相匹配的字符,記錄索引為i
    3. 如果上面的i是正常范圍內(nèi)(小于len),新建一個(gè)數(shù)組,長(zhǎng)度為len(原來(lái)的字符串的長(zhǎng)度),將i索引前面的字符逐一復(fù)制進(jìn)新數(shù)組里面,然后循環(huán) i<=x<len 的字符,將字符逐一復(fù)制進(jìn)新數(shù)組,但是這次的復(fù)制有規(guī)則,即如果那個(gè)字符與oldChar相同那么新數(shù)組對(duì)應(yīng)索引處就放newChar.
    4. 最后通過(guò)新建的數(shù)組new一個(gè)String對(duì)象返回

    思考:一開(kāi)始我覺(jué)得第二步好像沒(méi)什么必要性,沒(méi)有第二步其實(shí)也能實(shí)現(xiàn).但是,仔細(xì)想想,假設(shè)原字符串沒(méi)有查找到與oldChar匹配的字符,那么我們就可以規(guī)避去新建一個(gè)數(shù)組,從而節(jié)約了不必要的開(kāi)銷.可以,很棒,我們就是要追求極致的性能,減少浪費(fèi)資源.

    小細(xì)節(jié):源碼中有一個(gè)小細(xì)節(jié),注釋中有一句avoid getfield opcode,意思是避免getfield操作碼?

    image

    感覺(jué)那句代碼就是拷貝了一個(gè)引用副本啊,有什么高大上的作用?查閱文章https://blog.csdn.net/gaopu12345/article/details/52084218 后發(fā)現(xiàn)答案:在一個(gè)方法中需要大量引用實(shí)例域變量的時(shí)候,使用方法中的局部變量代替引用可以減少getfield操作的次數(shù)饭庞,提高性能戒悠。

public String replace(char oldChar, char newChar) {
    //1\. 如果兩者相同,那么就沒(méi)必要進(jìn)行比較了
    if (oldChar != newChar) {
        int len = value.length;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */

        //2\. 從最前面開(kāi)始,循環(huán)遍歷,找到與oldChar相同的字符
        while (++i < len) {
            if (val[i] == oldChar) {
                break;
            }
        }
        //3\. 如果找到了與oldChar相同的字符才進(jìn)入if
        if (i < len) {
            //4\. 新建一個(gè)數(shù)組,用于存放新數(shù)據(jù)
            char buf[] = new char[len];
            //5\. 將i前面的全部復(fù)制進(jìn)新數(shù)組里面去
            for (int j = 0; j < i; j++) {
                buf[j] = val[j];
            }
            //6\. 在i后面的字符,我們將其一個(gè)一個(gè)地放入新數(shù)組中,當(dāng)然在放入時(shí)需要比對(duì)是否和oldChar相同,相同則存放newChar
            while (i < len) {
                char c = val[i];
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            //7\. 最終重新new一個(gè)String
            return new String(buf, true);
        }
    }
    return this;
}

9.其他類方法

  • trim()

    功能:截去字符串兩端的空格,但對(duì)于中間的空格不處理
    大體實(shí)現(xiàn):記錄前面有st個(gè)空格,最后有多少個(gè)空格,那么長(zhǎng)度就減去多少個(gè)空格,最后根據(jù)上面的這2個(gè)數(shù)據(jù)去切割字符串.

public String trim() {
    int len = value.length;
    int st = 0;
    char[] val = value;    /* avoid getfield opcode */

    //1\. 記錄前面有多少個(gè)空格
    while ((st < len) && (val[st] <= ' ')) {
        st++;
    }
    //2\. 記錄后面有多少個(gè)空格
    while ((st < len) && (val[len - 1] <= ' ')) {
        len--;
    }
    //3\. 切割唄,注意:切割里面具體實(shí)現(xiàn)是重新new了一個(gè)String
    return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
  • startsWith(String prefix)或endsWith(String suffix)

    功能:用來(lái)比較當(dāng)前字符串的起始字符或子字符串prefix和終止字符或子字符串suffix是否和當(dāng)前字符串相同舟山,重載方法中同時(shí)還可以指定比較的開(kāi)始位置offset.

    思路:比較簡(jiǎn)單,就直接看代碼了,有詳細(xì)注釋.

public boolean startsWith(String prefix) {
    return startsWith(prefix, 0);
}
public boolean startsWith(String prefix, int toffset) {
    char ta[] = value;
    int to = toffset;
    char pa[] = prefix.value;
    int po = 0;
    int pc = prefix.value.length;
    // Note: toffset might be near -1>>>1.
    //1\. 入?yún)z測(cè)合法性
    if ((toffset < 0) || (toffset > value.length - pc)) {
        return false;
    }
    //2\. 循環(huán)進(jìn)行逐個(gè)字符遍歷,有不相等的就直接返回false,遍歷完了還沒(méi)發(fā)現(xiàn)不相同的,那么就是true
    while (--pc >= 0) {
        if (ta[to++] != pa[po++]) {
            return false;
        }
    }
    return true;
}
  • contains(String str)

    功能:判斷參數(shù)s是否被包含在字符串中绸狐,并返回一個(gè)布爾類型的值.
    思路:其實(shí)就是利用已經(jīng)實(shí)現(xiàn)好的indexOf()去查找是否包含.源碼中對(duì)于已實(shí)現(xiàn)的東西利用率還是非常高的.我們要多學(xué)習(xí).

public boolean contains(CharSequence s) {
    return indexOf(s.toString()) > -1;
}

10.基本類型轉(zhuǎn)換為字符串類型

這部分代碼一看就懂,都是一句代碼解決.

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}
public static String valueOf(char data[]) {
    return new String(data);
}
public static String valueOf(char data[], int offset, int count) {
    return new String(data, offset, count);
}
public static String copyValueOf(char data[], int offset, int count) {
    return new String(data, offset, count);
}
public static String copyValueOf(char data[]) {
    return new String(data);
}
public static String valueOf(boolean b) {
    return b ? "true" : "false";
}
public static String valueOf(char c) {
    char data[] = {c};
    return new String(data, true);
}
public static String valueOf(int i) {
    return Integer.toString(i);
}
public static String valueOf(long l) {
    return Long.toString(l);
}
public static String valueOf(float f) {
    return Float.toString(f);
}
public static String valueOf(double d) {
    return Double.toString(d);
}

注意事項(xiàng)

最后注意一下:Android 6.0(23) 源碼中,String類的實(shí)現(xiàn)被替換了捏顺,具體調(diào)用的時(shí)候六孵,會(huì)調(diào)用一個(gè)StringFactory來(lái)生成一個(gè)String.
來(lái)看下Android源碼中String,,我擦,,這…..直接拋錯(cuò)誤UnsupportedOperationException,可能是因?yàn)镺racle告Google的原因吧..

public String() {
    throw new UnsupportedOperationException("Use StringFactory instead.");
}
public String(String original) {
    throw new UnsupportedOperationException("Use StringFactory instead.");
}

我們平時(shí)開(kāi)發(fā)APP時(shí)都是使用的java.lang包下面的String,上面的問(wèn)題一般不會(huì)遇到,但是作為Android開(kāi)發(fā)者還是要了解一下.

三、AbstractStringBuilder源碼分析

先看看類StringBuffer和StringBuilder的繼承結(jié)構(gòu)

image

可以看到StringBuffer和StringBuilder都是繼承了AbstractStringBuilder.所以這里先分析一下AbstractStringBuilder.

在這基類里面真實(shí)的保存了StringBuffer和StringBuilder操作的實(shí)際數(shù)據(jù)內(nèi)容,數(shù)據(jù)內(nèi)容其實(shí)是一個(gè)char[] value;數(shù)組,在其構(gòu)造方法中其實(shí)就是初始化該字符數(shù)組.

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

1.擴(kuò)容

既然數(shù)據(jù)內(nèi)容(上面的value數(shù)組)是在AbstractStringBuilder里面的,那么很多操作我覺(jué)得應(yīng)該也是在父類里面,比如擴(kuò)容,下面我們看看源碼

public void ensureCapacity(int minimumCapacity) {
    if (minimumCapacity > 0)
        ensureCapacityInternal(minimumCapacity);
}

/**
* 確保value字符數(shù)組不會(huì)越界.重新new一個(gè)數(shù)組,引用指向value
*/    
private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

/**
* 擴(kuò)容:之前的大小的2倍+2
*/    
private int newCapacity(int minCapacity) {
    // overflow-conscious code   擴(kuò)大2倍+2
    //小知識(shí)點(diǎn):這里可能會(huì)溢出,溢出后是負(fù)數(shù)哈,注意
    int newCapacity = (value.length << 1) + 2;
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    //MAX_ARRAY_SIZE的值是Integer.MAX_VALUE - 8,先判斷一下預(yù)期容量(newCapacity)是否在0<x<MAX_ARRAY_SIZE之間,在這區(qū)間內(nèi)就直接將數(shù)值返回,不在這區(qū)間就去判斷一下是否溢出
    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;
}

可以看到這里的擴(kuò)容方式是 = 以前的大小*2+2,其他的細(xì)節(jié)方法中已給出詳細(xì)注釋.

2.追加

舉一個(gè)比較有代表性的添加,詳細(xì)注釋在代碼中

/**
* 追加:從指定字符串的片段
*/
public AbstractStringBuilder append(CharSequence s, int start, int end) {
    //1\. 如果是空,則添加字符串"null"
    if (s == null)
        s = "null";
    //2\. 判斷是否越界
    if ((start < 0) || (start > end) || (end > s.length()))
        throw new IndexOutOfBoundsException(
            "start " + start + ", end " + end + ", s.length() "
            + s.length());
    //3\. 記錄添加字符串長(zhǎng)度
    int len = end - start;
    //4\. 判斷一下 當(dāng)前數(shù)組長(zhǎng)度+需要添加的字符串長(zhǎng)度 是否夠裝,不夠裝就擴(kuò)容(擴(kuò)容時(shí)還有復(fù)制原內(nèi)容到新數(shù)組中)
    ensureCapacityInternal(count + len);
    //5\. 追加內(nèi)容到value數(shù)組最后
    for (int i = start, j = count; i < end; i++, j++)
        value[j] = s.charAt(i);
    //6\. 更新數(shù)組長(zhǎng)度
    count += len;
    return this;
}

3.增加

這里的大體思想是和以前大一的時(shí)候用C語(yǔ)言在數(shù)組中插入數(shù)據(jù)是一樣的.

這里假設(shè)需要插入的字符串s,插入在目標(biāo)字符串desOffset處,插入的長(zhǎng)度是len.首先將需要插入處的desOffset~desOffset+len往后挪,挪到desOffset+len處,然后在desOffset處插入目標(biāo)字符串.

大體思想就是這樣,是不是覺(jué)得很熟悉?? ヽ( ̄▽ ̄)?

下面這個(gè)方法是上面思路的具體實(shí)現(xiàn),詳細(xì)的邏輯分析已經(jīng)放到代碼注釋中.

//插入字符串,從dstOffset索引處開(kāi)始插入,插入內(nèi)容為s中的[start,end]字符串
public AbstractStringBuilder insert(int dstOffset, CharSequence s,
                                         int start, int end) {
    //1\. 空處理
    if (s == null)
        s = "null";
    //2\. 越界判斷
    if ((dstOffset < 0) || (dstOffset > this.length()))
        throw new IndexOutOfBoundsException("dstOffset "+dstOffset);
    //3\. 入?yún)z測(cè)是否合法
    if ((start < 0) || (end < 0) || (start > end) || (end > s.length()))
        throw new IndexOutOfBoundsException(
            "start " + start + ", end " + end + ", s.length() "
            + s.length());
    //4\. 長(zhǎng)度記錄
    int len = end - start;
    //5\. 判斷一下 當(dāng)前數(shù)組長(zhǎng)度+需要添加的字符串長(zhǎng)度 是否夠裝,不夠裝就擴(kuò)容(擴(kuò)容時(shí)還有復(fù)制原內(nèi)容到新數(shù)組中)
    ensureCapacityInternal(count + len);
    //6\. 將原數(shù)組中dstOffset開(kāi)始的count - dstOffset個(gè)字符復(fù)制到dstOffset + len處,,,,這里其實(shí)就是騰出一個(gè)len長(zhǎng)度的區(qū)間,用用戶存放目標(biāo)字符串,這個(gè)區(qū)間就是dstOffset到dstOffset + len
    System.arraycopy(value, dstOffset, value, dstOffset + len,
                     count - dstOffset);
    //7\. 存放目標(biāo)字符串
    for (int i=start; i<end; i++)
        value[dstOffset++] = s.charAt(i);
    //8\. 記錄字符串長(zhǎng)度
    count += len;
    //9\. 返回自身引用  方便鏈?zhǔn)秸{(diào)用
    return this;
}

4.刪除

源碼里面的刪除操作實(shí)際上是復(fù)制,比如下面這個(gè)方法刪除start到end之間的字符,實(shí)際是將以end開(kāi)始的字符復(fù)制到start處,并且將數(shù)組的長(zhǎng)度記錄count減去len個(gè)

//刪除從start到end索引區(qū)間( [start,end)前閉后開(kāi)區(qū)間 )內(nèi)內(nèi)容
public AbstractStringBuilder delete(int start, int end) {
    if (start < 0)
        throw new StringIndexOutOfBoundsException(start);
    if (end > count)
        end = count;
    if (start > end)
        throw new StringIndexOutOfBoundsException();
    int len = end - start;
    //當(dāng)start==end時(shí)不會(huì)改變
    if (len > 0) {
      //將value數(shù)組的start+len位置開(kāi)始的count-end個(gè)字符復(fù)制到value數(shù)組的start位置處.  注意,并且將數(shù)組count減去len個(gè).
        System.arraycopy(value, start+len, value, start, count-end);
        count -= len;
    }
    return this;
}

5.切割

我擦,,,,原來(lái)StringBuffer的切割效率并不高嘛,其實(shí)就是new了一個(gè)String….

public String substring(int start, int end) {
    if (start < 0)
        throw new StringIndexOutOfBoundsException(start);
    if (end > count)
        throw new StringIndexOutOfBoundsException(end);
    if (start > end)
        throw new StringIndexOutOfBoundsException(end - start);
    return new String(value, start, end - start);
}

6.改

改其實(shí)就是對(duì)其替換,而在源碼中替換最終的實(shí)現(xiàn)其實(shí)是復(fù)制(還是復(fù)制..( ̄▽ ̄)~*).

大體思路: 假設(shè)需要將字符串str替換value數(shù)組中的start-end中,這時(shí)只需將end后面的字符往后移動(dòng),在中間騰出一個(gè)坑,用于存放需要替換的str字符串.最后將str放到value數(shù)組中start索引處.

public AbstractStringBuilder replace(int start, int end, String str) {
    //1\. 入?yún)z測(cè)合法性
    if (start < 0)
        throw new StringIndexOutOfBoundsException(start);
    if (start > count)
        throw new StringIndexOutOfBoundsException("start > length()");
    if (start > end)
        throw new StringIndexOutOfBoundsException("start > end");
    if (end > count)
        end = count;
    //2\. 目標(biāo)String長(zhǎng)度
    int len = str.length();
    //3\. 計(jì)算新的數(shù)組的長(zhǎng)度
    int newCount = count + len - (end - start);
    //4\. 判斷一下是否需要擴(kuò)容
    ensureCapacityInternal(newCount);

    //5\. 將value數(shù)組的end位置開(kāi)始的count - end個(gè)字符復(fù)制到value數(shù)組的start+len處. 相當(dāng)于把end之后的字符移到最后去,然后中間留個(gè)坑,用來(lái)存放str(需要替換成的值)
    System.arraycopy(value, end, value, start + len, count - end);
    //6\. 這是String的一個(gè)方法,用于將str復(fù)制到value中start處  其最底層實(shí)現(xiàn)是native方法(getCharsNoCheck() )
    str.getChars(value, start);
    //7\. 更新count
    count = newCount;
    return this;
}

7.查詢

查詢是最簡(jiǎn)單的,就是返回?cái)?shù)組中相應(yīng)索引處的值.

public char charAt(int index) {
    if ((index < 0) || (index >= count))
        throw new StringIndexOutOfBoundsException(index);
    return value[index];
}

四幅骄、StringBuffer源碼分析

定義

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

StringBuffer和StringBuilder都是相同的繼承結(jié)構(gòu).都是繼承了AbstractStringBuilder.

StringBuffer和StringBuilder構(gòu)造方法,可以看到默認(rèn)大小是16,

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

1. 我們先來(lái)看看StringBuffer的append方法

啥,不就是調(diào)用父類的append方法嘛..


image

但是,請(qǐng) 注意:前面說(shuō)了StringBuffer是線程安全的,為什么,源碼里面使用了synchronized給方法加鎖了.

public synchronized StringBuffer append(boolean b) {
    toStringCache = null;
    super.append(b);
    return this;
}

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

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

2.StringBuffer的其他方法

幾乎都是所有方法都加了synchronized,幾乎都是調(diào)用的父類的方法.

public synchronized StringBuffer delete(int start, int end) {
    toStringCache = null;
    super.delete(start, end);
    return this;
}
public synchronized StringBuffer replace(int start, int end, String str) {
    toStringCache = null;
    super.replace(start, end, str);
    return this;
}
public synchronized int indexOf(String str, int fromIndex) {
    return super.indexOf(str, fromIndex);
}
...

五劫窒、StringBuilder分析

定義

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

1. 我們先來(lái)看看StringBuilder的append方法

啥,還是調(diào)用父類的append方法嘛..


image

但是,請(qǐng) 注意:前面說(shuō)了StringBuilder不是線程安全的,為什么,源碼里面沒(méi)有使用synchronized進(jìn)行加鎖.

public StringBuilder append(boolean b) {
    super.append(b);
    return this;
}

@Override
public StringBuilder append(char c) {
    super.append(c);
    return this;
}

@Override
public StringBuilder append(int i) {
    super.append(i);
    return this;
}

2.StringBuilder的其他方法

也是全部調(diào)用的父類方法. 但是是沒(méi)有加鎖的.

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

六、總結(jié)

  1. String,StringBuffer,StringBuilder最終底層存儲(chǔ)與操作的都是char數(shù)組.但是String里面的char數(shù)組是final的,而StringBuffer,StringBuilder不是,也就是說(shuō),String是不可變的,想要新的字符串只能重新生成String.而StringBuffer和StringBuilder只需要修改底層的char數(shù)組就行.相對(duì)來(lái)說(shuō),開(kāi)銷要小很多.
  2. String的大多數(shù)方法都是重新new一個(gè)新String對(duì)象返回,頻繁重新生成容易生成很多垃圾.
  3. 還是那句古話,StringBuffer是線程安全的,StringBuilder是線程不安全的.因?yàn)镾tringBuffer的方法是加了synchronized鎖起來(lái)了的,而StringBuilder沒(méi)有.
  4. 增刪比較多時(shí)用StringBuffer或StringBuilder(注意單線程與多線程)拆座。實(shí)際情況按需而取吧主巍,既然已經(jīng)知道了里面的原理。

學(xué)習(xí)源碼我們能從中收獲什么

  • Java的源碼都是經(jīng)過(guò)上千萬(wàn)(我亂說(shuō)的..哈哈)的程序員校驗(yàn)過(guò)的,不管是算法挪凑、命名孕索、doc文檔、寫(xiě)作風(fēng)格等等都非常規(guī)范躏碳,值得我們借鑒與深思搞旭。還有很多很多的小技巧。

  • 下次在使用時(shí)能按需而取菇绵,追求性能肄渗。

  • 避免項(xiàng)目中的很多錯(cuò)誤的發(fā)生。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咬最,一起剝皮案震驚了整個(gè)濱河市翎嫡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌永乌,老刑警劉巖惑申,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件具伍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡圈驼,警方通過(guò)查閱死者的電腦和手機(jī)人芽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)碗脊,“玉大人啼肩,你說(shuō)我怎么就攤上這事⊙昧妫” “怎么了祈坠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)矢劲。 經(jīng)常有香客問(wèn)我赦拘,道長(zhǎng),這世上最難降的妖魔是什么芬沉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任躺同,我火速辦了婚禮,結(jié)果婚禮上丸逸,老公的妹妹穿的比我還像新娘蹋艺。我一直安慰自己,他們只是感情好黄刚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布捎谨。 她就那樣靜靜地躺著,像睡著了一般憔维。 火紅的嫁衣襯著肌膚如雪涛救。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天业扒,我揣著相機(jī)與錄音检吆,去河邊找鬼。 笑死程储,一個(gè)胖子當(dāng)著我的面吹牛蹭沛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播章鲤,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼致板,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了兼贡?” 一聲冷哼從身側(cè)響起竞阐,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伦泥,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體喳瓣,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了怜珍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凤粗,死狀恐怖酥泛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嫌拣,我是刑警寧澤柔袁,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站异逐,受9級(jí)特大地震影響捶索,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜灰瞻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一腥例、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酝润,春花似錦燎竖、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蕉陋,卻和暖如春捐凭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凳鬓。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工茁肠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缩举。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓垦梆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親仅孩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子托猩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容