JDK源碼:String

概述

Java中使用String類來表示字符串,每個字符串都是String類的實例。由於String類是被final關鍵詞修飾的,因此實際上字符串都是常亮,變切實線程安全的。java

類定義

  String 類被聲明爲 final,說明它不能再被繼承。同時它實現了三個接口,分別爲 Serializable、Comparable 和 CharSequence。其中 Serializable 接口代表其能夠序列化。數組

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
複製代碼

主要字段

  ==value==是一個字符數組,被聲明爲final,是一個不可變數組,字符串實際上就是由這樣一個字符數組的形式存儲。
  ==hash==存儲的是String實例的hash值,可是隻有第一次調用hashCode()方法會計算hash值,而後會緩存下hash值,下次能夠直接調用。緩存

/** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
複製代碼

內部類

  String內部只有一個內部類CaseInsensitiveComparator,實現了Comparator, java.io.Serializable接口,分別提供比較器功能和序列化功能。
安全

private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable
複製代碼

  比較器內部主要的方法是compare()方法,提供比較字符串的功能。具體流程以下:bash

  1. 比較時,會以較短的那個字符串長度爲準比較,循環遍歷兩個字符串,比較是否每一個字符都相同,若是是大小寫不一樣會在轉換大小寫後再次比較。
  2. 若是確實出現有字不串不相同的,返回不一樣的字符二者之差(c1-c2),由於二者不是相同的字符串,返回的不多是0,也就表明着兩個字符串不相同。
  3. 若是比較完後發現所有字符相同,就返回兩字符長度之差(n1-n2),若是兩個字符串長度不一樣,那返回的結果仍然是一個非0的數字,表明兩字符串不相同。
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++) {//1
                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;//2
                        }
                    }
                }
            }
            return n1 - n2;//3
        }
複製代碼

構造方法

  String構造方法有不少,只挑選一部分分析。
  默認的無參構造函數和以String爲參數的構造方法都是經過直接給成員變量賦值完成。函數

public String() {
        this.value = "".value;
    }
    
        public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
複製代碼

  若是傳入的參數是一個字節數組,在賦值給value的時候不是直接賦值,而是從新複製一個數組,將新數組賦值給value,避免外部經過那個數組引用修改String內部的value。ui

public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
複製代碼

  傳入構造參數的參數重包含了編碼格式,可使用指定的編碼格式來建立字符串。this

public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(charsetName, bytes, offset, length);
    }
    
       public String(byte bytes[], int offset, int length, Charset charset) {
        if (charset == null)
            throw new NullPointerException("charset");
        checkBounds(bytes, offset, length);
        this.value =  StringCoding.decode(charset, bytes, offset, length);
    }
複製代碼

  傳入參數爲StringBuffer和StringBuilder,這個個類型的實例實際上表示的是可變字符串,內部存儲也是經過字節數組來存儲字符串信息,因此是直接講傳入的可變字符串內部的字符數組複製後,賦值給給value。編碼

public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
    
       public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }
複製代碼

主要方法

  返回字符串的長度,實際上就是內部字符數組的長度。spa

public int length() {
        return value.length;
    }
複製代碼

  判斷字符串是否爲空,實際上就是判斷內部字符數組長度是否爲0。

public boolean isEmpty() {
        return value.length == 0;
    }
複製代碼

  尋找指定索引處的字符,會首先判斷是否越界,若是沒有越界就返回內部字符數組對應索引的字符。

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

   返回指定索引處的碼點(字符對應的int值),會先判斷是否越界,而後調用Character類的方法獲得對應的碼點。

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

  將字符串中的一部分賦值給兩一個字符數組,是經過將字符串內部的數組複製後再賦值給目的數組。

void getChars(char dst[], int dstBegin) {
        System.arraycopy(value, 0, dst, dstBegin, value.length);
    }
複製代碼

  得到字符串對應的字節數組,能夠指定編碼格式。

public byte[] getBytes(String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null) throw new NullPointerException();
        return StringCoding.encode(charsetName, value, 0, value.length);
    }

    public byte[] getBytes(Charset charset) {
        if (charset == null) throw new NullPointerException();
        return StringCoding.encode(charset, value, 0, value.length);
    }

    public byte[] getBytes() {
        return StringCoding.encode(value, 0, value.length);
    }
複製代碼

  經過字方法來判斷兩個字符串是否相等。具體流程以下:

  1. 判斷應用是否相等,若是想等直接返回true。
  2. 判斷要比較的對象是不是String類型的實例,若是是,繼續進行比較。
  3. 判斷待比較的字符串長度是否和本字符串一致,若是一直就繼續比較。
  4. 循環遍歷兩個字符串的內部的字符數組的每個字符是否相等(沒有作大小寫轉換,大小寫不一樣則視爲不相同)。
public boolean equals(Object anObject) {
        if (this == anObject) {//1
            return true;
        }
        if (anObject instanceof String) {//2
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {//3
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {//4
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
複製代碼

  判斷是否和其餘字符序列內部的內容相同。其實主要是用來喝Stringbuffer和StringBuilder比較。具體流程以下:

  1. 判斷傳入的序列是不是AbstractBuilder類的子類的實例,若是是就繼續判斷。
  2. 判斷是不是StringBuffer類的實例,若果是就經過synchronized加鎖調用nonSyncContentEquals()方法保證線程安全(由於StringBuffer是線程安全的),若是不是那麼就直接調用。nonSyncContentEquals()方法內部經過遍歷內部的字符數組比較內容是否相等。若是都不是就繼續進行判斷。
  3. 判斷是不是字符串,若是是就調用equals()方法比較是否相等。若是不是就繼續進行判斷。
  4. 走到這一步說明這是一個通用的CharSequeeu,判斷這個序列是否和字符串長度相等。若是相等就進行遍歷,比較內部字符數組的內容是否相等。
public boolean contentEquals(CharSequence cs) {
        // Argument is a StringBuffer, StringBuilder
        if (cs instanceof AbstractStringBuilder) {//1
            if (cs instanceof StringBuffer) {//2
                synchronized(cs) {
                   return nonSyncContentEquals((AbstractStringBuilder)cs);
                }
            } else {
                return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        }
        // Argument is a String
        if (cs instanceof String) {//3
            return equals(cs);
        }
        // Argument is a generic CharSequence
        char v1[] = value;
        int n = v1.length;
        if (n != cs.length()) {//4
            return false;
        }
        for (int i = 0; i < n; i++) {
            if (v1[i] != cs.charAt(i)) {
                return false;
            }
        }
        return true;
    }
    
        private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
        char v1[] = value;
        char v2[] = sb.getValue();
        int n = v1.length;
        if (n != sb.length()) {
            return false;
        }
        for (int i = 0; i < n; i++) {
            if (v1[i] != v2[i]) {
                return false;
            }
        }
        return true;
    }
複製代碼

  忽略大小寫,基佬兩個字符串是否相等。先判斷引用是否相等,相等直接返回true,若是不相等就判斷帶比較的字符串是否爲空,長度是否和本字符串相等,而且調用regionMatches()方法是否返回爲true,若是都知足返回true,表示相等。regionMatches()方法是忽略大小寫的狀況下比較兩個字符串的子串是否相等。

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

  標膠兩個字符串的的大小。具體流程以下:

  1. 得到兩個字符串中較短的那個字符串的長度,賦值給lim。
  2. 循環遍歷兩個字符串內部的字節數組,直到遍歷到第limci,比較字符是否相等,若是不相等就返回兩字符之差(c1-c2),若是都相等就返回兩字符串長度之差,只有兩個字符串徹底相等時,纔會返回0.
public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);//1
        char v1[] = value;
        char v2[] = anotherString.value;

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

  此方法時能夠指定字符串前綴開始的偏移量,而後判斷是偏移量開始的位置是否與前綴字符串相同。判斷是否以某字符串爲前綴和後綴都是基於此實現。具體流程以下:

  1. 判斷偏移量是否合法若是不合法直接返回false。
  2. 循環遍歷(從指定的偏移量開始),比較是否相等,若是都相等就返回true,不然仍是返回false。
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.
        if ((toffset < 0) || (toffset > value.length - pc)) {//1
            return false;
        }
        while (--pc >= 0) {//2
            if (ta[to++] != pa[po++]) {
                return false;
            }
        }
        return true;
    }

複製代碼

  計算hash值。具體流程以下:

  1. 判斷hash的值是否爲0,若是爲0,說明尚未初始化,同時判斷value的長度是否大於零,若是不大於,說明次字符串時空字符串,默認hash值爲0,不須要再作哈希運算。
  2. 遍歷字符數組,運算公式是h = 31 * h + val[i],有兩個好處:第一,字符串靠前的字符運算出來的hash值比重更大,若是是對字符串作hash運算,前綴相近的能更大概率哈希到比較近的位置,可以彙集前綴類似的數據(局部性原理);第二,選擇的31是素數,能更好的離散數據(爲何是31而不是其餘素數我也不太明白,不過31的二進制表示每一位都爲1)。
public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {//1
            char val[] = value;

            for (int i = 0; i < value.length; i++) {//2
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
複製代碼
相關文章
相關標籤/搜索