前言
相信作為 JAVAER
,平時編碼時使用最多的必然是 String
字串,而相信應該存在不少人對於 String
的 api
很熟悉了,但沒有看過其原始碼實現,其實我個人覺得對於 api 的使用,最開始的階段是看其官方文件,而隨著開發經驗的積累,應當嘗試去看原始碼實現,這對自身能力的提升是至關重要的。當你理解了原始碼之後,後面對於 api 的使用也會更加得心應手!
備註:以下記錄基於 jdk8 環境
String 只是一個類
String
其實只是一個類,我們大致可以從以下幾個角度依次剖析它:
- 類繼承關係
- 類成員變數
- 類構造方法
- 類成員方法
- 相關靜態方法
繼承關係
從 IDEA
自帶外掛匯出 String
的 UML 類圖如下:
從圖中馬上可以看出,String
實現了介面 Serializable
,Comparable
,CharSequence
,簡單介紹一下這三個介面的作用:
-
Serializable
:實現該介面的類將具備序列化的能力,該介面沒有任何實現,僅僅是一直標識作用。 -
Comparable
:實現此介面的類具備比較大小的能力,比如實現此介面的物件的列表(和陣列)可以由Collections
類的靜態方法sort
進行自動排序。 -
CharSequence
:字元序列統一的我介面。提供字元序列通用的操作方法,通常是一些只讀方法,許多字元相關的類都實現此介面,以達到對字元序列的操作,比如:String
,StringBuffer
等。
String
類定義如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence{
...
}
由 final
修飾符可知, String
類是無法被繼承,不可變類。
類成員變數
這裡主要介紹最關鍵的一個成員變數 value[]
,其定義如下:
/** The value is used for character storage. */
private final char value[];
String
是一個字串,由字元 char
所組成,因此實際上 String
內部其實就是一個字元陣列,用 value[]
表示,注意這裡的 value[] 是用 final 修飾的,表示該值是不允許修改的。
類構造方法
String
有很多過載的構造方法,介紹如下:
-
空引數構造方法,初始化字串例項,預設為空字元,理論上不需要用到這個構造方法,實際上定義一個空字元
String = ""
就會初始化一個空字串的String
物件,而此構造方法,也是把空字元的value[]
拷貝一遍而已,原始碼實現如下:public String() { this.value = "".value; }
-
通過一個字串引數構造
String
物件,實際上 將形參的value
和hash
賦值給例項物件作為初始化,相當於深拷貝了一個形參String
物件,原始碼如下:public String(String original) { this.value = original.value; this.hash = original.hash; }
-
通過字元陣列去構建一個新的
String
物件,這裡使用Arrays.copyOf
方法拷貝字元陣列public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }
-
在源字元陣列基礎上,通過偏移量(起始位置)和字元數量,擷取構建一個新的
String
物件。public String(char value[], int offset, int count) { //如果偏移量小於0,則丟擲越界異常 if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { //如果字元數量小於0,則丟擲越界異常 if (count < 0) { throw new StringIndexOutOfBoundsException(count); } //在擷取的字元數量為0的情況下,偏移量在字串長度範圍內,則返回空字元 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); } //使用Arrays.copyOfRange靜態方法,擷取一定範圍的字元陣列,從offset開始,長度為offset+count,賦值給當前例項的字元陣列 this.value = Arrays.copyOfRange(value, offset, offset+count); }
-
在源整數陣列的基礎上,通過偏移量(起始位置)和字元數量,擷取構建一個新的
String
物件。這裡的整數陣列表示字元對應的ASCII整數值public String(int[] codePoints, int offset, int count) { //如果偏移量小於0,則丟擲越界異常 if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { //如果字元數量小於0,則丟擲越界異常 if (count < 0) { throw new StringIndexOutOfBoundsException(count); } //在擷取的字元數量為0的情況下,偏移量在字串長度範圍內,則返回空字元 if (offset <= codePoints.length) { this.value = "".value; return; } } // Note: offset or count might be near -1>>>1. 如果偏移量大於字元總長度-擷取的字元長度,則丟擲越界異常 //if (offset > codePoints.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } final int end = offset + count; // 這段邏輯是計算出字元陣列的精確大小n,過濾掉一些不合法的int資料 int n = count; for (int i = offset; i < end; i++) { int c = codePoints[i]; if (Character.isBmpCodePoint(c)) continue; else if (Character.isValidCodePoint(c)) n++; else throw new IllegalArgumentException(Integer.toString(c)); } // 按照上一步驟計算出來的陣列大小初始化陣列 final char[] v = new char[n]; //遍歷填充字元陣列 for (int i = offset, j = 0; i < end; i++, j++) { int c = codePoints[i]; if (Character.isBmpCodePoint(c)) v[j] = (char)c; else Character.toSurrogates(c, v, j++); } //賦值給當前例項的字元陣列 this.value = v; }
-
通過源位元組陣列,按照一定範圍,從offset開始擷取length個長度,初始化
String
例項,同時可以指定字元編碼。public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException { //字元編碼引數為空,丟擲空指標異常 if (charsetName == null) throw new NullPointerException("charsetName"); //靜態方法 檢查位元組陣列的索引是否越界 checkBounds(bytes, offset, length); //使用 StringCoding.decode 將位元組陣列按照一定範圍解碼為字串,從offset開始擷取length個長度 this.value = StringCoding.decode(charsetName, bytes, offset, length); }
-
與第6個構造相似,只是編碼引數過載為
Charset
型別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); }
-
通過源位元組陣列,構造一個字串例項,同時指定字元編碼,具體實現其實是呼叫第6個構造器,起始位置為0,擷取長度為位元組陣列長度
public String(byte bytes[], String charsetName) throws UnsupportedEncodingException { this(bytes, 0, bytes.length, charsetName); }
-
通過源位元組陣列,構造一個字串例項,同時指定字元編碼,具體實現其實是呼叫第7個構造器,起始位置為0,擷取長度為位元組陣列長度
public String(byte bytes[], Charset charset) { this(bytes, 0, bytes.length, charset); }
-
通過源位元組陣列,按照一定範圍,從offset開始擷取length個長度,初始化
String
例項,與第六個構造器不同的是,使用系統預設字元編碼public String(byte bytes[], int offset, int length) { //檢查索引是否越界 checkBounds(bytes, offset, length); //使用系統預設字元編碼解碼位元組陣列為字元陣列 this.value = StringCoding.decode(bytes, offset, length); }
-
通過源位元組陣列,構造一個字串例項,使用系統預設編碼,具體實現其實是呼叫第10個構造器,起始位置為0,擷取長度為位元組陣列長度
public String(byte bytes[]) { this(bytes, 0, bytes.length); }
-
將
StringBuffer
構建成一個新的String
,比較特別的就是這個方法有synchronized
鎖 同一時間只允許一個執行緒對這個buffer
構建成String
物件,是執行緒安全的public String(StringBuffer buffer) { //對當前 StringBuffer 物件加同步鎖 synchronized(buffer) { //拷貝 StringBuffer 字元陣列給當前例項的字元陣列 this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); } }
-
將
StringBuilder
構建成一個新的String
,與第12個構造器不同的是,此構造器不是執行緒安全的public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length()); }
類成員方法
-
獲取字串長度,實際上是獲取字元陣列長度
public int length() { return value.length; }
-
判斷字串是否為空,實際上是盼復字元陣列長度是否為0
public boolean isEmpty() { return value.length == 0; }
-
根據索引引數獲取字元
public char charAt(int index) { //索引小於0或者索引大於字元陣列長度,則丟擲越界異常 if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } //返回字元陣列指定位置字元 return value[index]; }
-
根據索引引數獲取指定字元ASSIC碼(int型別)
public int codePointAt(int index) { //索引小於0或者索引大於字元陣列長度,則丟擲越界異 if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } //返回索引位置指定字元ASSIC碼(int型別) return Character.codePointAtImpl(value, index, value.length); }
-
返回index位置元素的前一個元素的ASSIC碼(int型)
public int codePointBefore(int index) { //獲得index前一個元素的索引位置 int i = index - 1; //檢查索引是否越界 if ((i < 0) || (i >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return Character.codePointBeforeImpl(value, index, 0); }
-
方法返回的是程式碼點個數,是實際上的字元個數,功能類似於length(),對於正常的String來說,length方法和codePointCount沒有區別,都是返回字元個數。但當String是Unicode型別時則有區別了。例如:String str = “/uD835/uDD6B” (即使 `Z` ), length() = 2 ,codePointCount() = 1
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); }
-
也是相對Unicode字符集而言的,從index索引位置算起,偏移codePointOffset個位置,返回偏移後的位置是多少,例如,index = 2 ,codePointOffset = 3 ,maybe返回 5
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); }
-
這是一個不對外的方法,是給String內部呼叫的,因為它是沒有訪問修飾符的,只允許同一包下的類訪問 引數:dst[]是目標陣列,dstBegin是目標陣列的偏移量,既要複製過去的起始位置(從目標陣列的什麼位置覆蓋) 作用就是將String的字元陣列value整個複製到dst字元陣列中,在dst陣列的dstBegin位置開始拷貝
void getChars(char dst[], int dstBegin) { System.arraycopy(value, 0, dst, dstBegin, value.length); }
-
得到char字元陣列,原理是getChars() 方法將一個字串的字元複製到目標字元陣列中。 引數:srcBegin是原始字串的起始位置,srcEnd是原始字串要複製的字元末尾的後一個位置(既複製區域不包括srcEnd) dst[]是目標字元陣列,dstBegin是目標字元的複製偏移量,複製的字元從目標字元陣列的dstBegin位置開始覆蓋。
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 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); }
簡單的總結
-
String
被 修飾符final
修飾,是無法被繼承的,不可變類 -
String
實現Serializable
介面,可以被序列化 -
String
實現Comparable
介面,可以用於比較大小 -
String
實現CharSequence
介面,表示一直有序字元序列,實現了通用的字元序列方法 -
String
是一個字元序列,內部資料結構其實是一個字元陣列,所有的操作方法都是圍繞這個字元陣列的操作。 -
String
中頻繁使用到了System
類的arraycopy
方法,目的是拷貝字元陣列
最後
由於篇幅原因,String
第一篇總結先到這裡,後續部分將寫另外寫一遍記錄,會第一時間推送公眾號【張少林同學】,歡迎關注!