java.lang.String原始碼分析
java.lang.String是使用頻率非常高的類。要想更好的使用java.lang.String類,瞭解其原始碼實現是非常有必要的
一、String類
String類是被final所修飾的,所以不允許被繼承和修改,String類實現了Serializable、Comparable、CharSequence這三個介面,Serializable介面使得String可序列化;Comparable為String提供了比較器,使其可進行排序;CharSequence介面有length(),charAt(int index),subSequence(int start,int end)方法。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
二、String屬性
String聲明瞭4個變數
//儲存字串的字元陣列。該陣列為final變數,一旦賦值,將不會更改。 private final char value[]; //一個int型的變數hash用來存放計算後的該String的雜湊值 private int hash; // Default to 0 //提供序列化的ID private static final long serialVersionUID = -6849794470754667710L; //聲明瞭一個可序列化的欄位 private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
三、String構造方法
java.lang.String物件構造方法比較多,列舉如下:
public String() public String(String original) public String(char value[]) public String(char value[], int offset, int count) public String(int[] codePoints, int offset, int count) @Deprecated public String(byte ascii[], int hibyte, int offset, int count) @Deprecated public String(byte ascii[], int hibyte) public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException public String(byte bytes[], int offset, int length, Charset charset) public String(byte bytes[], String charsetName) throws UnsupportedEncodingException public String(byte bytes[], Charset charset) public String(byte bytes[], int offset, int length) public String(byte bytes[]) public String(StringBuffer buffer)
在 public String(StringBuffer buffer) 中,傳入形參為StringBuffer,StringBuffer為執行緒安全類。則在此構造方法內部進行了synchronized關鍵字鎖同步。程式碼如下:
public String(StringBuffer buffer) { synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); } }
在 public String(StringBuilder builder) 中,傳入形參為StringBuilder,StringBuilder為非執行緒安全類。則在此構造方法內部內部未做同步處理,對比 public String(StringBuffer buffer) 。程式碼如下:
public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length()); }
四、String常用方法
java.lang.String物件中封裝方法非常多,僅針對常用方法原始碼進行分析。如:equals(),replace(), indexOf(),startsWith(),compareTo(),regionMathes(),hashCode()。
public boolean equals(Object anObject)
用於比較兩物件儲存內容是否相同。採用比較巧妙的方式進行排除比較:(1)先“==”比較兩物件是否是同一物件,若是,直接返回true, 否則進一步判斷;(2)判斷待比較物件型別是否是java.lang.String,若不是,直接返回false,否則進一步判斷;(3)判斷兩字串長度是否相等,若不是直接返回false,否則進一步判斷;(4)從字元陣列中第一個字元開始,依次進行比較,一旦發現不相同字元直接返回false,若所在字元均相同則返回true。對字元陣列中字元依次進行比較是一件非常耗時的操作,將此操作放在最後執行,先利用其它條件進行對其進行判斷。
public boolean equals(Object anObject) { //如果引用的是同一個物件,返回真 if (this == anObject) { return true; } //如果不是String型別的資料,返回假 if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; //如果char陣列長度不相等,返回假 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; }
public String replace(char oldChar, char newChar)
將字串中指定字元替換為新的字元。(1)先判斷待替換字元和新字元是否相同,若相同,則直接返回原字串,若不同,則繼續執行;(2)找出第一次出現待替換字元位置i,建立新的等長字元陣列,將該位置之前的字元依次放入新的字元陣列中;(3)從位置i處依次遍歷比較原字元陣列中字元是否是待替換字元,若是,則將新字元放入新字元陣列對應位置,若不是,則將原字元陣列中字元放入對應位置。巧妙做了一個小優化,直接找出第一次出現待替換字元的位置,再從此處開始遍歷,提高效率。
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 replace(CharSequence target, CharSequence replacement)
該方法是我們通常意義所用到的 public String replace(String target, String replacement) ,java.lang.String實現了java.lang.CharSequence介面。方法內部呼叫正則表示式匹配替換來實現。
public String replace(CharSequence target, CharSequence replacement) { return Pattern.compile(target.toString(), Pattern.LITERAL).matcher( this).replaceAll(Matcher.quoteReplacement(replacement.toString())); }
public int indexOf(String str)
該方法是找出目標字串是第一次出現指定子字串的位置,若不存在,則返回-1,若存在,則返回位置座標。具體實現是呼叫 static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex) 方法。先對目標字串中出現子字串的位置可能範圍,然後在此範圍中遍歷找出與子字串第一個字元相同的位置,並對後面字元進行比較分析。
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); } static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex) { if (fromIndex >= sourceCount) { return (targetCount == 0 ? sourceCount : -1); } if (fromIndex < 0) { fromIndex = 0; } if (targetCount == 0) { return fromIndex; } char first = target[targetOffset]; int max = sourceOffset + (sourceCount - targetCount); for (int i = sourceOffset + fromIndex; i <= max; i++) { /* Look for first character. */ if (source[i] != first) { while (++i <= max && source[i] != first); } /* Found first character, now look at the rest of v2 */ if (i <= max) { int j = i + 1; int end = j + targetCount - 1; for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++); if (j == end) { /* Found whole string. */ return i - sourceOffset; } } } return -1; }
public int compareTo(String anotherString)
該方法是對字串集合進行排序的基礎,通過此方法可比較兩字串大小,原理很簡單,原始碼如下:
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; }
public boolean startsWith(String prefix)
判斷目標字串是否以指定字元子串開關,該方法內部是呼叫 public boolean startsWith(String prefix, int toffset) 方法實現,原理很簡單,程式碼如下:
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; /*如果開始查詢的位置小於0或大於當前字串長度與指定字首長度的差值,則返回false*/ if ((toffset < 0) || (toffset > value.length - pc)) { return false; } //從此字串的指定索引開始比較是否與指定字首相等 while (--pc >= 0) { if (ta[to++] != pa[po++]) { //不相等返回false return false; } } //相等返回true return true; }
public int hashCode()
其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; }
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)
將一個String字串,按照給定的引數複製到目標字元陣列的方法。其中傳入4個引數:int型別的srcBegin為字串中要複製的第一個字元的索引;int型別的srcEnd為字串中要複製的最後一個字元之後的索引(要複製的最後一個字元位於索引 srcEnd-1 處);char型別的陣列dst[]為目標陣列;int型別的desBegin為目標陣列中的起始偏移量。
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { if (srcBegin < 0) { throw new StringIndexOutOfBoundsException(srcBegin); } if (srcEnd > value.length) { throw new StringIndexOutOfBoundsException(srcEnd); } if (srcBegin > srcEnd) { throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); } System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); }
public String concat(String str)
將指定字串連線到此字串的結尾,如果引數字串的長度為 0,則返回此 String 物件。否則,建立一個新的 String 物件,用來表示由此 String 物件表示的字元序列和引數字串表示的字元序列連線而成的字元序列。以下是原始碼:
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
public String 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; }
public String substring(int beginIndex, int endIndex)
返回一個新字串,它是此字串的一個子字串。該子字串從指定的 beginIndex 處開始,直到索引 endIndex - 1 處的字元。因此,該子字串的長度為 endIndex-beginIndex。該方法包含兩個int型別的引數,分別是: beginIndex - 起始索引(包括),endIndex - 結束索引(不包括)。原始碼如下:
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); } /*如果起始索引為0並且結束索引為此字串的長度則返回此字串,否則建立新的字串*/ return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); }