相信做爲 JAVAER
,平時編碼時使用最多的必然是 String
字符串,而相信應該存在很多人對於 String
的 api
很熟悉了,但沒有看過其源碼實現,其實我我的以爲對於 api 的使用,最開始的階段是看其官方文檔,而隨着開發經驗的積累,應當嘗試去看源碼實現,這對自身能力的提高是相當重要的。當你理解了源碼以後,後面對於 api 的使用也會更加駕輕就熟!java
備註:如下記錄基於 jdk8 環境api
String
其實只是一個類,咱們大體能夠從如下幾個角度依次剖析它:數組
從 IDEA
自帶插件導出 String
的 UML 類圖以下:安全
從圖中立刻能夠看出,String
實現了接口 Serializable
,Comparable
,CharSequence
,簡單介紹一下這三個接口的做用:數據結構
Serializable
:實現該接口的類將具有序列化的能力,該接口沒有任何實現,僅僅是一直標識做用。Comparable
:實現此接口的類具有比較大小的能力,好比實現此接口的對象的列表(和數組)能夠由 Collections
類的靜態方法 sort
進行自動排序。CharSequence
:字符序列統一的我接口。提供字符序列通用的操做方法,一般是一些只讀方法,許多字符相關的類都實現此接口,以達到對字符序列的操做,好比:String
,StringBuffer
等。String
類定義以下:ui
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{ ... }
由 final
修飾符可知, String
類是沒法被繼承,不可變類。this
這裏主要介紹最關鍵的一個成員變量 value[]
,其定義以下:編碼
/** The value is used for character storage. */ private final char value[];
String
是一個字符串,由字符 char
所組成,所以實際上 String
內部其實就是一個字符數組,用 value[]
表示,注意這裏的 value[] 是用 final 修飾的,表示該值是不容許修改的。spa
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
第一篇總結先到這裏,後續部分將寫另外寫一遍記錄,會第一時間推送公衆號【張少林同窗】,歡迎關注!