1. 程式人生 > >JDK1.8原始碼閱讀——String

JDK1.8原始碼閱讀——String

    人不醜話也不多,直接開始啦~

一、結構預覽

    由於String包含的方法是在太多,因此這裡就不列出它的結構樹了。給大家看一下本篇部落格的主要內容的目錄吧。

1. String類屬性

2. String成員變數

3. String構造器

4. String是如何重寫equals()的

5. String常用的工具方法

二、正文

1. String類屬性

    首先看一下String類的定義:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence 

    可以看到String繼承了序列化介面、Comparable介面以及CharSequence 介面

    Serializable:序列化介面,至於為什麼要實現這個介面,個人覺得可能與資料的儲存與傳輸有關。由於String的應用範圍實在是太廣了,例如我們常用的資料庫連線資訊、http、socket埠和IP等都是字串,又比如redis、memcache等快取服務也使用了String作為快取型別,而這些應用場景,都與序列化密不可分。以上屬於個人理解,不當之處,可以指出。

    Comparable:用於比較字串的大小,具體的實現方法在下文。

    CharSequence:有序的字符集,CharSequence就是字元序列,String, StringBuilder和StringBuffer本質上都是通過字元陣列實現的,CharSequence 是 char 值的一個可讀序列。此介面對許多不同種類的 char 序列提供統一的只讀訪問。

2. String成員變數

private final char value[];

private int hash;

private static final long serialVersionUID = -6849794470754667710L;

    所以,一個字串是由一個序列化標識、雜湊值、儲存字串的char陣列 組成的。

    注意:一個空字串的雜湊值為0,原因在下文介紹

3. String構造器

    

    可以看到,構造一個String的方法是在是太多了,這裡就不做介紹了,主要說一下下面幾點:

    3.1  String str = "" 與 new String()

    這裡要提到java中的一個概念:常量池。那麼什麼是(String)常量池呢?

    由於Java中String的應用是在是太廣泛了,可以說是隨處可見。為了避免資源的浪費,重複的去建立相同的字串,所以Java執行時會維護一個String Pool(String池), 也叫“字串緩衝區”。String池用來存放執行時中產生的各種字串,並且池中的字串的內容不重複。

    使用String str = ""去構造一個字串時,會去常量池中去查詢池中是否存在這樣一個字串,如果存在,則直接飲用此字串,如果不存在,再去建立一個新的字串並維護到池中。

    new String()則不進行判斷,直接建立一個新的字串。示例如下:

String str1 = "helloworld";
String str2 = new String("helloworld");
String str3 = "hello"+"world";
System.out.println(str1 == str2);	//執行結果為:false
System.out.println(str2 == str3);	//執行結果為:false
System.out.println(str1 == str3);	//執行結果為:true

    3.2  intern

public native String intern();

    這裡順帶提一下intern這個方法,它的作用是返回當前String的記憶體地址或字串。當呼叫intern方法時,如果池中已經包含一個與該String確定的字串相同equals(Object)的字串,則返回該字串。否則,將此String物件新增到池中,並返回此物件的引用。

String str1 = "helloworld";
String str2 = str1.intern();
System.out.println(str2);		//列印helloworld

4 equals與hashCode

    在上篇部落格中,我們知道如果重寫equals方法,那麼必須重寫hashCode方法。String對Object的equals進行了重寫,使其判斷的是字串內容是否相等,而不是引用地址。所以String的hashCode也是對字串的內容進行hash

    equals的原始碼如下:

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

    重寫後的equals,首先對其引用地址進行判斷,如果不同,再判斷其型別。在滿足型別相同的條件下,獲取其儲存字串的變數value,首先對長度進行判斷,這樣意味著“helloworld”與“hello world”是不相等的。如果長度相同,再通過迴圈去比較value陣列的每一個元素是否相同,只要存在一個元素不同,返回false。

    上文說過,hashCode返回的是其字串內容的雜湊值,原始碼如下:

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

    注意這裡的判斷是如果hash為0或者字串長度>0,才會去計算hash值,否則返回預設的hash值0(int的預設值為0)。

    關於這裡為何使用31,而不是定義其他的常量,網上大致的解釋有兩點,這裡僅做參考:

    a.  31本身就是幾個優質的質因數之一,使用31能夠減少hash碰撞

    b.  31=2<<5-1,效率故而較高

5.  String常用的方法

5.1  isEmpty

public boolean isEmpty() {
    return value.length == 0;
}

    用於判斷字串是否為空,不包括null的情形,因此使用這個方法前需要去考慮它是否為null。

5.2  charAt

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

    用於獲取指定位置的字元,使用該方法需要對字串長度進行判斷,否則有可能出現下標越界的異常。

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

    比較兩個字串的大小,本質上是比較阿斯克碼的大小,通常用於比較排序。需要注意的是,通過迴圈的內容,我們可以發現排序的優先順序是從前往後的,當靠前的字元不相等時,就會直接返回,不再繼續比較。

5.4  indexOf

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

        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;
            for (int i = fromIndex; i < max; i++) {
                if (value[i] == ch) {
                    return i;
                }
            }
            return -1;
        } else {
            return indexOfSupplementary(ch, fromIndex);
        }
    }
    private int indexOfSupplementary(int ch, int fromIndex) {
        if (Character.isValidCodePoint(ch)) {
            final char[] value = this.value;
            final char hi = Character.highSurrogate(ch);
            final char lo = Character.lowSurrogate(ch);
            final int max = value.length - 1;
            for (int i = fromIndex; i < max; i++) {
                if (value[i] == hi && value[i + 1] == lo) {
                    return i;
                }
            }
        }
        return -1;
    }

    indexOf通常用於判斷某個字元是否存在於當前字串並獲取其下標,indexOf支援從0開始查詢,也支援從指定的下標位置開始查詢,如果不存在,返回-1。

5.5  substring

    substring有多種引數的實現方法,這裡只介紹substring(int beginIndex),畢竟它們實現的方法基本相同。其原始碼如下:

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

    相對於indexOf,substring沒有處理beginIndex<0的情況,也沒有處理beginIndex超出字串長度的情況,都是直接丟擲了下標越界的異常,因此我們在使用時,一定要注意避免這種情況。

     substring的實現方法為:String(char value[], int offset, int 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);
            }
            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);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

    在這個方法裡,主要關注的是Arrays提供的方法:public static char[] copyOfRange(char[] original, int from, int to),通過這裡我們可以知道,substring的實現是通過陣列的拷貝實現的,而陣列的拷貝底層是通過System.arraycopy這個non-java方法實現的。

5.6  split

    我們常用的split(String regex)方法其底層是通過split(String regex, int 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);
    }

5.7  trim

    去除字串頭尾的空格,中間部分的空格是不做處理的。原始碼如下:

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

        while ((st < len) && (val[st] <= ' ')) {
            st++;
        }
        while ((st < len) && (val[len - 1] <= ' ')) {
            len--;
        }
        return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
    }

   其原理就是通過阿斯克碼值得判斷去擷取字串。

5.8  valueOf

    獲取當前物件的String,大致包括以下方法:

    valueOf(Object obj)

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

        添加了null判斷,最終呼叫Object的toString方法。

    valueOf(boolean b) 

public static String valueOf(boolean b) {
     return b ? "true" : "false";
}

        很簡單,就是一個三元表示式,返回true或false的字串。

    除此之外,還包括一些基本資料型別的包裝類的valueOf方法,其底層都是呼叫對應的toString方法實現的,由於涉及到其他類的原始碼知識,這裡不做介紹。