最近打算開始來讀一下JDK的部分源碼,此次先從咱們平時用的最多的String類(JDK1.8)開始,本文主要會對如下幾個方法的源碼進行分析:java
equals
hashCode
equalsIgnoreCase
indexOf
startsWith
concat
substring
split
trim
compareTo
若是有不對的地方請多多指教,那麼開始進入正文。git
首先看下String
類實現了哪些接口正則表達式
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
這個序列化接口沒有任何方法和域,僅用於標識序列化的語意。算法
這個接口只有一個compareTo(T 0)接口,用於對兩個實例化對象比較大小。數組
這個接口是一個只讀的字符序列。包括length(), charAt(int index), subSequence(int start, int end)這幾個API接口,值得一提的是,StringBuffer和StringBuild也是實現了該接口。緩存
看一下兩個主要變量:函數
/** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0
能夠看到,value[]
是存儲String的內容的,即當使用String str = "abc";的時候,本質上,"abc"是存儲在一個char類型的數組中的。性能
而hash
是String實例化的hashcode
的一個緩存。由於String常常被用於比較,好比在HashMap中。若是每次進行比較都從新計算hashcode的值的話,那無疑是比較麻煩的,而保存一個hashcode的緩存無疑能優化這樣的操做。優化
注意:這邊有一個須要注意的點就是能夠看到value
數組是用final
修飾的,也就是說不能再去指向其它的數組,可是數組的內容是能夠改變的,之因此說String
不可變是由於其提供的API(好比replace等方法)都會給咱們返回一個新的String
對象,而且咱們沒法去改變數組的內容,這纔是它不可變的緣由。ui
equals() 方法用於判斷 Number 對象與方法的參數進是否相等
String類重寫了父類Object的equals
方法,來看看源碼實現:
instanceof
判斷目標對象是不是String類型或其子類的實例,若是不是的話則返回falsehashCode() 方法用於返回字符串的哈希碼
Hash算法就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。在Java中,全部的對象都有一個int hashCode()
方法,用於返回hash碼。
根據官方文檔的定義:Object.hashCode()
函數用於這個函數用於將一個對象轉換爲其十六進制的地址。根據定義,若是2個對象相同,則其hash碼也應該相同。若是重寫了 equals() 方法,則原 hashCode() 方法也一併失效,因此也必需重寫 hashCode() 方法。
按照上面源碼舉例說明:
String msg = "abcd"; System.out.println(msg.hashCode());
此時value = {'a','b','c','d'} 所以for循環會執行4次
第一次:h = 31*0 + a = 97
第二次:h = 31*97 + b = 3105
第三次:h = 31*3105 + c = 96354
第四次:h = 31*96354 + d = 2987074
由以上代碼計算能夠算出 msg 的hashcode = 2987074
在源碼的hashcode的註釋中還提供了一個多項式計算方式:
s[0] 31^(n-1) + s[1]31^(n-2) + ... + s[n-1]
另外,咱們能夠看到,計算中使用了31
這個質數做爲權進行計算。能夠儘量保證數據分佈更分散
在《Effective Java》中有說起:
之因此選擇31,是由於它是一個奇素數。若是乘數是偶數,而且乘法溢出的話,信息就會丟失,由於與2相乘等價於移位運算。使用素數的好處並不明顯,可是習慣上都使用素數來計算散列結果。31有個很好的特性。即用移位和減法來代替乘法,能夠獲得更好的性能:31 * i == (i << 5) - i。現代的VM能夠自動完成這種優化。
/** Cache the hash code for the string */ private int hash; // Default to 0
並且如上面所示,當計算完以後會用一個變量hash把哈希值保存起來,下一次再獲取的時候就不用換從新計算了,正是由於String的不可變性保證了hash值的惟一。
equalsIgnoreCase() 方法用於將字符串與指定的對象比較,不考慮大小寫
接下來來看看源碼實現:
來看看核心方法
相信看了上圖的介紹就能看懂了,這裏就很少說了。
查找指定字符或字符串在字符串中第一次出現地方的索引,未找到的狀況返回 -1
String str = "wugui"; System.out.println(str.indexOf("g"));
輸出結果:2
public int indexOf(String str) { return indexOf(str, 0); } public int indexOf(String str, int fromIndex) { return indexOf(value, 0, value.length,str.value, 0, str.value.length, fromIndex); }
接下來是咱們的核心方法,先看下各個參數的介紹
/* * @param source 被搜索的字符 * @param sourceOffset 原字符串偏移量 * @param sourceCount 原字符串大小 * @param target 要搜索的字符 * @param targetOffset 目標字符串偏移量 * @param targetCount 目標字符串大小 * @param fromIndex 開始搜索的位置 */ static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex) { ...... }
下面是代碼的邏輯步驟
在indexOf
的源碼裏面我認爲邊界條件是寫的比較好的
咱們這裏假設
String str = "wugui"; str.indexOf("ug");
在上圖第2步,計算出max做爲下面循環的邊界條件
//找到第一個匹配的字符索引 if (source[i] != first) { while (++i <= max && source[i] != first); }
咱們計算出 max
=3,也就是說咱們在使用迭代搜索第一個字符的時候只須要遍歷到索引爲3的位置,就能夠了,由於索引第4位也就是最後一位 'i'
,就是匹配到了第一個字符也是無心義的,由於咱們要搜索的目標自字符是2位字符,同第5步計算出end
做爲邊界條件也是一樣的道理。
有了indexOf
方法以後,那有些方法就能夠借用它來實現了,好比contains
方法,源碼以下:
public boolean contains(CharSequence s) { return indexOf(s.toString()) > -1; }
只須要調用根據indexOf的返回值來判斷是否包含目標字符串就能夠了。
startsWith() 方法用於檢查字符串是不是以指定子字符串開頭,若是是則返回 True,不然返回 False
String str = "wugui"; System.out.println(str.startsWith("wu"));
輸出結果:true
public boolean startsWith(String prefix) { return startsWith(prefix, 0); } public boolean startsWith(String prefix, int toffset) { ...... }
既然有了startsWith
方法,那麼endsWith
就很容易實現了,以下:
只要修改一下參數,設置偏移量就能夠了。
用於將指定的字符串參數鏈接到字符串上
String str1 = "wu"; String str2 = "gui"; System.out.println(str1.concat(str2));
輸出結果:wugui
能夠看到是使用了Arrays.copyOf
方法來生成新數組
char buf[] = Arrays.copyOf(value, len + otherLen);
咱們來看看其實現:
能夠看到主要使用system.arraycopy
方法,點進去看一下實現:
若是看不到的話咱們這裏舉個例子:
好比 :咱們有一個數組數據
byte[] srcBytes = new byte[]{2,4,0,0,0,0,0,10,15,50};//原數組 byte[] destBytes = new byte[5]; //目標數組
咱們使用System.arraycopy進行復制
System.arrayCopy(srcBytes,0,destBytes ,0,5)
上面這段代碼就是 : 建立一個一維空數組,數組的總長度爲 12位,而後將srcBytes源數組中 從0位 到 第5位之間的數值 copy 到 destBytes目標數組中,在目標數組的第0位開始放置,
那麼這行代碼的運行效果應該是 2,4,0,0,0,
調用完Arrays.copy返回新數組方法後,會調用str.getChars(buf, len)來拼接字符串,咱們看下其實現:
能夠看到其實也是調用了System.arraycopy
來實現,這裏再也不細說。
最後一步就是把新數組賦值給value
return new String(buf, true);
提取字符串中介於兩個指定下標之間的字符
String str = "wugui"; System.out.println(str.substring(1, 3));//包括索引1不包括索引3
輸出結果:ug
來看看 new String(value, beginIndex, subLen)
的實現
看看Arrays.copyOfRange
是如何實現的:
能夠看到其實仍是使用的System.arraycopy
來實現,上面已經介紹過了,這裏再也不細說。
根據匹配給定的正則表達式來拆分字符串
先來看看用法:
public String[] split(String regex, int limit)
第一個參數regex
表示正則表達式,第二個參數limit
是分割的子字符串個數
String str = "a:b:c:d"; String[] split = str.split(":");
當沒有傳limit
參數默認調用的是split(String regex, 0)
上面的輸出爲:[a, b, c, d]
若是把limit
參數換成2那麼輸出結果變成:[a, b:c:d],能夠看出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; //若是regex只有一位,且不爲列出的特殊字符; //若是regex有兩位,第一位爲轉義字符且第二位不是數字或字母 //第三個是和編碼有關,就是不屬於utf-16之間的字符 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); }
接下來咱們一步步來分析:
能夠看到有三個條件:
regex
只有一位,且不爲列出的特殊字符regex
有兩位,第一位爲轉義字符且第二位不是數字或字母utf-16
之間的字符只有知足上面三個條件才能進入下一步:
第一次分割時,使用off
和next
,off
指向每次分割的起始位置,next
指向分隔符的下標,完成一次分割後更新off
的值,當list的大小等於limit-1
時,直接添加剩下子字符串,具體看下源碼:
最後就是對子字符串進行處理:
我的以爲這部分源碼仍是比較難的,有興趣的同窗能夠再去研究一下。
刪除字符串的頭尾空白符
String str = " wugui "; System.out.println(str.trim());
輸出:wugui
這部分仍是比較簡單的,這裏再也不細說。
比較兩個字符
String a = "a"; String b = "b"; System.out.println(a.compareTo(b));
輸出:-1
看看源碼:
有關String的源碼暫時分析到這裏,其它的源碼感興趣的小夥伴能夠按本身去研究一下,接下來可能會得寫幾篇文章來介紹一下Java中的包裝類,敬請期待!