[Java8源碼閱讀]String

Java中String不是基本數(shù)據(jù)類型,而是一種特殊的類。

String代表的是不可變的字符序列(被final修飾),為不可變對(duì)象,一旦被創(chuàng)建,就不能修改它的值,對(duì)于已經(jīng)存在的String對(duì)象的修改都是重新創(chuàng)建一個(gè)新的對(duì)象,然后把新的值保存進(jìn)去。

源碼分析

屬性

String中有兩個(gè)較為重要的屬性:

private final char value[];
private int hash;

從value[]可以看出,String是通過(guò)字符數(shù)組的方式實(shí)現(xiàn)的。hash用于保存當(dāng)前字符串的hash值。

構(gòu)造方法

// 通過(guò)該構(gòu)造函數(shù)的String值為空字符串
public String()
// 使用字符串構(gòu)造函數(shù)
public String(String original)
// 字符串?dāng)?shù)組構(gòu)造函數(shù)
public String(char value[])
// 從傳入value數(shù)組中offset位置(包含offset)開始,截取count個(gè)字符構(gòu)造String
public String(char value[], int offset, int count)
// 基本同上
public String(int[] codePoints, int offset, int count)
// 下面兩個(gè)為過(guò)時(shí)構(gòu)造函數(shù)
public String(byte ascii[], int hibyte, int offset, int count)
public String(byte ascii[], int hibyte)
// 使用byte[]字節(jié)數(shù)組構(gòu)造String
public String(byte bytes[], int offset, int length, String charsetName)
public String(byte bytes[], int offset, int length, Charset charset)
public String(byte bytes[], String charsetName)
public String(byte bytes[], Charset charset)
public String(byte bytes[], int offset, int length)
public String(byte bytes[])
// 使用StringBuffer和StringBuilder構(gòu)造String
public String(StringBuffer buffer)
public String(StringBuilder builder)
/** 
 * 保護(hù)類型的構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)比較特別
 * 1. 傳入share并未使用,share主要作用是為了和上面String(char[] value)做區(qū)別
 * 2. String(char[] value)方法在創(chuàng)建String的時(shí)候會(huì)用到 會(huì)用到Arrays的copyOf方法將value中的內(nèi)容逐一復(fù)制到String當(dāng)中,而這個(gè)String(char[] value, boolean share)方法則是直接將value的引用賦值給String的value。那么也就是說(shuō),這個(gè)方法構(gòu)造出來(lái)的String和參數(shù)傳過(guò)來(lái)的char[] value共享同一個(gè)數(shù)組
 **/
String(char[] value, boolean share){
    this.value = value;
}

方法

charAt、codePointAt、codePointBefore、codePointCount、offsetByCodePoints

獲取并返回索引對(duì)應(yīng)的字符:

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

獲取并返回索引對(duì)應(yīng)字符的Unicode編碼:

public int codePointAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return Character.codePointAtImpl(value, index, value.length);
}

獲取并返回給定索引前面的Unicode代碼點(diǎn):

public int codePointBefore(int index) {
    int i = index - 1;
    if ((i < 0) || (i >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return Character.codePointBeforeImpl(value, index, 0);
}

準(zhǔn)確計(jì)算unicode字符的數(shù)量:

public int codePointCount(int beginIndex, int endIndex) {
    if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
        throw new IndexOutOfBoundsException();
    }
    return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
}

獲取索引偏移后指定代碼點(diǎn)的索引:

public int offsetByCodePoints(int index, int codePointOffset) {
    if (index < 0 || index > value.length) {
        throw new IndexOutOfBoundsException();
    }
    return Character.offsetByCodePointsImpl(value, 0, value.length,
            index, codePointOffset);
}

getBytes

在創(chuàng)建String的時(shí)候,可以使用byte[]數(shù)組,將一個(gè)字節(jié)數(shù)組轉(zhuǎn)換成字符串,同樣,我們可以將一個(gè)字符串轉(zhuǎn)換成字節(jié)數(shù)組,那么String提供了很多重載的getBytes方法。但是,值得注意的是,在使用這些方法的時(shí)候一定要注意編碼問(wèn)題。比如:

String s = "你好,世界!"; 
byte[] bytes = s.getBytes();

這段代碼在不同的平臺(tái)上運(yùn)行得到結(jié)果是不一樣的。由于我們沒(méi)有指定編碼方式,所以在該方法對(duì)字符串進(jìn)行編碼的時(shí)候就會(huì)使用系統(tǒng)的默認(rèn)編碼方式,比如在中文操作系統(tǒng)中可能會(huì)使用GBK或者GB2312進(jìn)行編碼,在英文操作系統(tǒng)中有可能使用iso-8859-1進(jìn)行編碼。這樣寫出來(lái)的代碼就和機(jī)器環(huán)境有很強(qiáng)的關(guān)聯(lián)性了,所以,為了避免不必要的麻煩,我們要指定編碼方式。如使用以下方式:

String s = "你好,世界!"; 
byte[] bytes = s.getBytes("utf-8");

replace、replaceAll、replaceFirst

  • replace的參數(shù)是char和CharSequence,即可以支持字符的替換,也支持字符串的替換
  • replaceAll和replaceFirst的參數(shù)是regex,即基于規(guī)則表達(dá)式的替換,比如,可以通過(guò)replaceAll(“\d”, “*”)把一個(gè)字符串所有的數(shù)字字符都換成星號(hào);

相同點(diǎn)是都是全部替換,即把源字符串中的某一字符或字符串全部換成指定的字符或字符串, 如果只想替換第一次出現(xiàn)的,可以使用 replaceFirst(),這個(gè)方法也是基于規(guī)則表達(dá)式的替換,但與replaceAll()不同的是,只替換第一次出現(xiàn)的字符串;

另外,如果replaceAll()和replaceFirst()所用的參數(shù)據(jù)不是基于規(guī)則表達(dá)式的,則與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) {
            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;
}
public String replaceAll(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
public String replaceFirst(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}

split

按照字符regex將字符串分成limit份:

public String[] split(String regex, int limit) {
    /* fastpath if the regex is a
     (1)one-char String and this character is not one of the
        RegEx's meta characters ".$|()[{^?*+\\", or
     (2)two-char String and the first char is the backslash and
        the second is not the ascii digit or ascii letter.
     */
    char ch = 0;
    if (((regex.value.length == 1 &&
         ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
         (regex.length() == 2 &&
          regex.charAt(0) == '\\' &&
          (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
          ((ch-'a')|('z'-ch)) < 0 &&
          ((ch-'A')|('Z'-ch)) < 0)) &&
        (ch < Character.MIN_HIGH_SURROGATE ||
         ch > Character.MAX_LOW_SURROGATE))
    {
        int off = 0;
        int next = 0;
        boolean limited = limit > 0;
        ArrayList<String> list = new ArrayList<>();
        while ((next = indexOf(ch, off)) != -1) {
            if (!limited || list.size() < limit - 1) {
                list.add(substring(off, next));
                off = next + 1;
            } else {    // last one
                //assert (list.size() == limit - 1);
                list.add(substring(off, value.length));
                off = value.length;
                break;
            }
        }
        // If no match was found, return this
        if (off == 0)
            return new String[]{this};

        // Add remaining segment
        if (!limited || list.size() < limit)
            list.add(substring(off, value.length));

        // Construct result
        int resultSize = list.size();
        if (limit == 0) {
            while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
                resultSize--;
            }
        }
        String[] result = new String[resultSize];
        return list.subList(0, resultSize).toArray(result);
    }
    return Pattern.compile(regex).split(this, limit);
}

equals、contentEquals、equalsIgnoreCase

equals:

  1. 如果兩個(gè)對(duì)象指向地址值一樣,就返回true;
  2. 判斷傳入類型是否為String類型
  3. 先判斷長(zhǎng)度是否一樣,在循環(huán)判斷每個(gè)字符是否相等
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

contentEquals:
接收StringBuffer對(duì)象,比較兩個(gè)內(nèi)容是否相等

public boolean contentEquals(StringBuffer sb) {
    return contentEquals((CharSequence)sb);
}

public boolean contentEquals(CharSequence cs) {
    // Argument is a StringBuffer, StringBuilder
    if (cs instanceof AbstractStringBuilder) {
        if (cs instanceof StringBuffer) {
            synchronized(cs) {
               return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        } else {
            return nonSyncContentEquals((AbstractStringBuilder)cs);
        }
    }
    // Argument is a String
    if (cs instanceof String) {
        return equals(cs);
    }
    // Argument is a generic CharSequence
    char v1[] = value;
    int n = v1.length;
    if (n != cs.length()) {
        return false;
    }
    for (int i = 0; i < n; i++) {
        if (v1[i] != cs.charAt(i)) {
            return false;
        }
    }
    return true;
}

equalsIgnoreCase:忽略字符串大小寫進(jìn)行比較是否相等

public boolean equalsIgnoreCase(String anotherString) {
    return (this == anotherString) ? true
            : (anotherString != null)
            && (anotherString.value.length == value.length)
            && regionMatches(true, 0, anotherString, 0, value.length);
}

compareTo:比較兩個(gè)字符串大小

compareTo

public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2;
        }
        k++;
    }
    return len1 - len2;
}

subsString

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 substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}

intern

public native String intern();

該方法返回一個(gè)字符串對(duì)象的內(nèi)部化引用。 String類維護(hù)一個(gè)初始為空的字符串的對(duì)象池,當(dāng)intern方法被調(diào)用時(shí),如果對(duì)象池中已經(jīng)包含這一個(gè)相等的字符串對(duì)象則返回對(duì)象池中的實(shí)例,否則添加字符串到對(duì)象池并返回該字符串的引用。

相關(guān)面試題

如果自己寫一個(gè)String類,路徑與Java中String類路徑相同,會(huì)發(fā)生什么?

這題和Java的類加載機(jī)制有關(guān),Java采用雙親委派機(jī)制,如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把請(qǐng)求委托給父加載器去完成,依次向上,因此,所有的類加載請(qǐng)求最終都應(yīng)該被傳遞到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器在它的搜索范圍中沒(méi)有找到所需的類時(shí),即無(wú)法完成該加載,子加載器才會(huì)嘗試自己去加載該類。
所有對(duì)于用戶自定義的String類,不會(huì)被加載,當(dāng)然,也不會(huì)被使用。

String,StringBuilder,StringBuffer的區(qū)別?

String是字符串常量,final修飾;StringBuffer字符串變量(線程安全);
StringBuilder 字符串變量(線程不安全).

  • String和StringBuffer

String和StringBuffer主要區(qū)別是性能:String是不可變對(duì)象,每次對(duì)String類型進(jìn)行操作都等同于產(chǎn)生了一個(gè)新的String對(duì)象,然后指向新的String對(duì)象.所以盡量不在對(duì)String進(jìn)行大量的拼接操作,否則會(huì)產(chǎn)生很多臨時(shí)對(duì)象,導(dǎo)致GC開始工作,影響系統(tǒng)性能.

StringBuffer是對(duì)對(duì)象本身操作,而不是產(chǎn)生新的對(duì)象,因此在有大量拼接的情況下,建議使用StringBuffer.

但是需要注意現(xiàn)在JVM會(huì)對(duì)String拼接做一定的優(yōu)化:

String s = “This is only ” + ”simple” + ”test”

會(huì)被虛擬機(jī)直接優(yōu)化成String s=“This is only simple test”,此時(shí)就不存在拼接過(guò)程.

  • StringBuffer和StringBuilder

StringBuffer是線程安全的可變字符串,其內(nèi)部實(shí)現(xiàn)是可變數(shù)組.StringBuilder是jdk 1.5新增的,其功能和StringBuffer類似,但是非線程安全.因此,在沒(méi)有多線程問(wèn)題的前提下,使用StringBuilder會(huì)取得更好的性能.

你對(duì)String對(duì)象的intern()熟悉么?

intern()方法會(huì)首先從常量池中查找是否存在該常量值,如果常量池中不存在則現(xiàn)在常量池中創(chuàng)建,如果已經(jīng)存在則直接返回.
比如 :

String s1=”aa”; 
String s2=s1.intern(); 
System.out.print(s1==s2);//返回true

參考資料:

Java 7 源碼學(xué)習(xí)系列(一)——String
Java7為什么要修改substring的實(shí)現(xiàn)
JDK源碼分析之String篇

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

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,638評(píng)論 18 399
  • Tip:筆者馬上畢業(yè)了,準(zhǔn)備開始 Java 的進(jìn)階學(xué)習(xí)計(jì)劃。于是打算先從 String 類的源碼分析入手,作為后面...
    石先閱讀 12,107評(píng)論 16 58
  • 轉(zhuǎn)自:http://blog.csdn.net/jackfrued/article/details/4492194...
    王帥199207閱讀 8,801評(píng)論 3 93
  • 器材:vivox6 后期:黃油相機(jī) 三月里,百花開。 海棠不畏春日寒,風(fēng)里雨里花開遍。 桃花李花爭(zhēng)著艷,蜂兒蝶兒繞...
    語(yǔ)花慢閱讀 474評(píng)論 3 1
  • 每個(gè)人身邊總會(huì)發(fā)生大大小小的事,而我每天卻徘徊在這些小事當(dāng)中。 最近,學(xué)校搞宿舍文化節(jié),通知每個(gè)宿舍取名字,我不是...
    江易依閱讀 171評(píng)論 0 0

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