1. 程式人生 > >java String類,底層自學自看 筆記(實時更新) 1

java String類,底層自學自看 筆記(實時更新) 1

提示:本文章是基於jdk1.7,對於一些常見類底層學習的公開筆記,學藝不精,發現錯誤請評論提出。

按照jdk,String類自上而下的順序挨個學習研究

package java.lang;
import java.io.ObjectStreamField;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Formatter;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

一:首先String型別實現了三個介面
Serializable:序列化,(可被轉為二進位制,實現永久儲存)
Comparable:自然排序(String提供了比較方法)
CharSequence:可讀序列()
二:擁有兩個私有實體類

private final  char value[];  字元陣列

分析:String型別的儲存是以字元陣列的方式進行儲存。

private int hash;  雜湊 生成的hash碼

private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];(宣告一個類的序列化欄位)

三:方法

1

public String() {
        this.value = new char[0];
}
可以  String i = new String(); 的方式建立一個字串物件,他會建立一個0長度的char型別陣列
public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

注:一個沒有什麼用的構造器,字串本身就是不可變的,直接 ‘=’ 就可以。
從記憶體意義上講,也許設計之初是,將一個不會再用到的字串呼叫此方法進行替換,不佔用新的記憶體,也許想法是達到節省記憶體的目的,但是顯然,這不符合String的特性。

之前都是熱身,現在開始稍微,有點難度-.-


2

public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
}

注:這個方法是new String時傳入一個Char型別陣列。
然後使用Arrays.copyOf,copy整個陣列,生成一個新的一模一樣的字元陣列返回給String。
且此處也不適合使用=直接記憶體賦值,容易產生一些不可預知的事情。


3

public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

注:這個其實去上面的是一樣的功能,只是增加了控制copy的起點和字元個數。


4

public String(int[] codePoints, int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);//起點不能小於0
        }
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);//個數不能小於0
        }
        if (offset > codePoints.length - count) {//不允許起點+個數大於了總長
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        final int end = offset + count;//結束位置
        int n = count;//需要獲取的個數
        for (int i = offset; i < end; i++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))//判斷是否是有效的hash點,簡單說就是是否小與2的16次方
                continue;
            else if (Character.isValidCodePoint(c))//簡單說這個是判斷他右移16位是否小於17
                n++;
            else throw new IllegalArgumentException(Integer.toString(c));
        }
	//這個for迴圈 就是判斷 這個hashcode碼值是否在 2的16 到 2的22之間,如果在 就n+1,就是增加一個位置
        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;
    }

注:這個方法從外層來看,是把一個char型別指定起點和位置的字元拿出來展示出來。
但以底層的形式來看,輸入5個不見得一定會返回5個,也可能是6個或者更多。
我猜測應該是防止碼值擴充套件而導致該方法無法使用,增加了該方法的擴充套件性。
2018-11-14 15:52- -1.0.0//該方法解讀存在修改可能,記錄下版本


接下來是幾個已經被廢棄的。
5

//功能:從8位整數值的陣列的子陣列中分配一個新的構造。
//hibyte:每個16位Unicode碼單元的前8位
@Deprecated //廢棄註解
    public String(byte ascii[], int hibyte, int offset, int count) {
        checkBounds(ascii, offset, count);//這個方法就是驗證起點和個數的和不能超過最大值,否則異常
        char value[] = new char[count];//建立一個新的,長度是count
        if (hibyte == 0) {
            for (int i = count; i-- > 0;) {
                value[i] = (char)(ascii[i + offset] & 0xff);
            }
        } else {
            hibyte <<= 8;
            for (int i = count; i-- > 0;) {
                value[i] = (char)(hibyte | (ascii[i + offset] & 0xff)); 做了二進位制非運算
            }
        }
        //該if執行的是如果給了Unicode碼
        this.value = value;
    }
//官方給的廢棄原因是-- 此方法沒有正確地將位元組轉換為字元

注:通過觀察,在hibyte有值的情況下確實沒有正確的將位元組轉化為字元,筆者並沒有看懂做二進位制非運算這部操作的意義和用途,儘管他實現了分配新的構造。通過實驗生成的是亂碼
猜測:該方法的目的可能是改變記憶體地址之類的,但是錯誤的直接改變內裡儲存的值(廢棄)
最新猜測:
2018-11-14 16:46- -1.0.0//該方法解讀存在修改可能,記錄下版本


@Deprecated//廢棄 this引用的就是上面那個方法,所以廢棄了
//該方法就是預設從頭到尾
    public String(byte ascii[], int hibyte) {
        this(ascii, hibyte, 0, ascii.length);
}

6

//這就是一個簡單的私有的工具類
//起始位置和需要的長度不能小於0
//其實值+需要值不能大於總長度
//之後再出現這個方法將不再進行解釋
private static void checkBounds(byte[] bytes, int offset, int length) {
        if (length < 0)//
            throw new StringIndexOutOfBoundsException(length);
        if (offset < 0)
            throw new StringIndexOutOfBoundsException(offset);
        if (offset > bytes.length - length)
            throw new StringIndexOutOfBoundsException(offset + length);
 }

7

//charsetName  要轉碼的名字
public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(charsetName, bytes, offset, length);//轉碼操作之後裡面會copy一個新的你要的char[]返回
}

注:這個是String提供的,一般對於亂碼問題才會用到的方法,先將字串轉成原始的byte然後轉碼。
同時提供了預設從頭至尾的轉碼,預設為ISO-8859-1

public String(byte bytes[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);//呼叫了上述方法,從起始到結尾
}

8

//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);
}
//以及他的另一種形式
public String(byte bytes[], Charset charset) {
        this(bytes, 0, bytes.length, charset);
}

9

//及直接生成預設編碼的構造方法,該decode的底層預設是UTF-8
public String(byte bytes[], int offset, int length) {
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(bytes, offset, length);
}
//以及他的簡化構造方法
public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
}

*轉碼一共就是這些,底層提供了輸入String型別轉碼,物件的形式轉碼,以及簡化版轉碼。
應用:如果直接以byte陣列的形式建立String,那麼直接就是utf-8了,非常的方便。



10

//將StringBuffer轉為String,StringBuffer和StringBuilder的底層都是char[] ,而且Buffer除了對其方法做了大量的synchronized之外二者並沒有什麼區別。
public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());//傳統的char型別複製
        }
}

Arrays.copy(char[],length);複製的陣列,長度
他的底層核心:
System.arraycopy(char[],int,char[],int,int);
底層是System.arraycopy(現有陣列(char[]),現有陣列起點(int),要複製內容進去的目標陣列(char[]),目標陣列複製內容的起點 , 複製的長度);

複製的長度一般都是現有陣列和目標陣列長度比較的較小值。
Math.min(one(int), two(int));會返回一個較小的one或者two
Math.min()的底層實現也很簡單

public static int min(int a, int b) {
        return (a <= b) ? a : b;
}

下一個方法是
11

//和buffer一樣,不浪費時間了-.-
public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

12

String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
}//這是指定是否允許該字串被斷開,被拆分等操作。
//官方所希望的功能顯然並沒有完成,這是一個毫無功能的構造方法,也許以後的版本會完善
//String型別本身也是進行某種操作後會生成新的String,這個功能也顯得不大可能

13

//這個是擷取String,前面就有這個方法,這個this呼叫的也是序號3的方法
@Deprecated
    String(int offset, int count, char[] value) {
        this(value, offset, count);
}//感覺沒什麼用,僅僅只是換了下引數的順序,廢棄也是正常的
//java公司出的原始碼書籍也提到過,構造方法僅僅換個引數是他們不推薦做的,因為這樣做增加了程式碼體積
,實際上也沒有任何功能上的豐富和加強。

構造方法結束


14

//獲取字串的長度
public int length() {
        return value.length;//value是char[]型別也是獲取陣列的長度就是string型別的長度
}

15

public boolean isEmpty() {
        return value.length == 0;
}
//空格不算是空,依舊有長度。
//如果value為null會報錯
//如果為""則是0

16

//返回指定位置的字元
public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {//不能小於0或者大於他的長度,否則會丟擲異常
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
}
//索引是從0開始的

17

//返回指定位置的code值
public int codePointAt(int index) {
        if ((index < 0) || (index >= value.length)) {//驗證
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointAtImpl(value, index, value.length);
}
//Character.codePointAtImpl該工具也不僅僅是做了返回操作,還是驗證了一些東西
static int codePointAtImpl(char[] a, int index, int limit) {
        char c1 = a[index++];//這裡是先返回index,再加1
        if (isHighSurrogate(c1)) {//十六進位制比較:c1>='\uD800'   && c1 < \uDBFF +1.   簡單說就是在 55296和56319+1 之間①   
            if (index < limit) {
                char c2 = a[index];//高位在左邊,低位在右邊
                if (isLowSurrogate(c2)) {//大於等於56320和<57343+1之間
                    return toCodePoint(c1, c2);
                }
            }
        }
        return c1;//如果是普通的字元直接返回碼點
    }

①:是補充字元驗證,比如說一個特殊字元,它在char陣列儲存要佔兩個位置來儲存,那麼在這裡就用到了。因為標識這個特殊字元同時需要兩個位置來儲存
注:如果該位置是補充字元,那麼就會尋找低位字元,然後做了一步toCodePoint運算,就是把之前分離儲存的字元再逆運算組合起來,都是一些int型別二進位制的位運算,在此不過多研究。


18

//返回輸入位置的前一個字元。
public int codePointBefore(int index) {
        int i = index - 1;
        if ((i < 0) || (i >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointBeforeImpl(value, index, 0);
}//和上一個比,就是輸出你要的前一個,沒啥好說的

static int codePointBeforeImpl(char[] a, int index, int start) {
        char c2 = a[--index];//這是直接給c2輸出減完後的
        if (isLowSurrogate(c2)) {
            if (index > start) {
                char c1 = a[--index];
                if (isHighSurrogate(c1)) {
                    return toCodePoint(c1, c2);
                }
            }
        }
        return c2;
}

19

//返回指定起點終點字元所對應字串的個數
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);
}
//驗證
public static int codePointCount(char[] a, int offset, int count) {
        if (count > a.length - offset || offset < 0 || count < 0) {
            throw new IndexOutOfBoundsException();
        }
        return codePointCountImpl(a, offset, count);//調下面那個方法,未做任何操作
    }

    static int codePointCountImpl(char[] a, int offset, int count) {
        int endIndex = offset + count;
        int n = count;
        for (int i = offset; i < endIndex; ) {
            if (isHighSurrogate(a[i++]) && i < endIndex &&
                isLowSurrogate(a[i])) {//遇到補碼  i+1  長度減1
                n--;
                i++;
            }
        }
        return n;
}

注:這應該是面向框架,或者內部呼叫的,於實際應用,一般用不上


20

//返回偏移量的方法
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);
}
//由指定索引開始,正數向左便宜,負數向右偏移。返回之後的字元在char陣列中的第幾位。
//解釋一下,比如果abcde中的c佔了兩個字元還儲存,那麼這個方法(0,3)就是4,索引 1是a,2是b,3和4是c
//用到的非常少,對這個方法進行追溯,發現只有一個sun公司自己開發的包用過,還只用過一次。

21

//複製字串
void getChars(char dst[], int dstBegin) {
        System.arraycopy(value, 0, dst, dstBegin, value.length);//這是一個java層面最底層的複製陣列的方法,
        //所有複製陣列,及底層是陣列的字串複製的話底層都會是這個方法。
       //第一個引數,要被複制的陣列物件
       //第二個引數,複製的起點
       //第三個引數,複製到另一個數組裡的目標陣列
       //第四個引數,開始複製的起點
       //第五個,複製的長度
}

22

//與上個方法功能相同
//引數1:複製的起點  2: 複製的重點 3:目標陣列  4:目標陣列複製進去時的起點
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);
        //上個方法已經詳細說明,此處不再過多討論。
}

23

//這是一個廢棄的方法,遠古時期jdk1.1用到的,遠古時期的東西一直遺留到了現在。
@Deprecated//廢棄
    public void getBytes(int srcBegin, int srcEnd, byte 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);
        }
        int j = dstBegin;
        int n = srcEnd;
        int i = srcBegin;
        char[] val = value;   /* avoid getfield opcode */

        while (i < n) {
            dst[j++] = (byte)val[i++];
        }
}
//試了一下這個方法,各種問題,比方說只有為0的複製進去了。官方解釋此方法不能正確的將字元轉化為位元組,
//這就可能是因為jvm的機制更改,底層功能的更改,因為此方法表面上看並沒有轉位元組的操作

24

//比較常用的一個方法,返回指定名稱字元編碼的byte陣列
public byte[] getBytes(String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null) throw new NullPointerException();
        return StringCoding.encode(charsetName, value, 0, value.length);//看似簡單 操作很深(1)
}
(1)
static byte[] encode(String charsetName, char[] ca, int off, int len)
        throws UnsupportedEncodingException
    {
        StringEncoder se = deref(encoder);//encode是ThreadLocal類,實現資料的隔離。
        //使用ThreadLocal的作用就是,當為該字串進行操作轉碼的時候,防止該物件發生了某些改變,就會發生很多意料之外的錯誤
        String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;//如果不指定,預設的字元編碼
        if ((se == null) || !(csn.equals(se.requestedCharsetName())
                              || csn.equals(se.charsetName()))) {
            se = null;
            try {
                Charset cs = lookupCharset(csn);//該方法底涉及的很深,大部分是驗證,所有關鍵的操作都放到了C語言,所以也沒有
                //必要寫出來,但是實現的東西很簡單。就是把輸出的字串生成對應的字元編碼物件
                if (cs != null)
                    se = new StringEncoder(cs, csn);
                    //這是一個生成StringEncode的靜態工程①
            } catch (IllegalCharsetNameException x) {}
            if (se == null)
                throw new UnsupportedEncodingException (csn);//嚴謹的驗證,一般不會跑到這裡
            set(encoder, se);//這一步就是把StringEncoder物件放進ThreadLocal裡。encoder就是ThreadLocal,同時也是
            //StringEncoder物件的一個實體類。由StringEncoder物件自己呼叫
        }
        return se.encode(ca, off, len);②//準備工作完成,開始最後一步
}
(1)①
private StringEncoder(Charset cs, String rcn) {
            this.requestedCharsetName = rcn;
            this.cs = cs;
            this.ce = cs.newEncoder()
                .onMalformedInput(CodingErrorAction.REPLACE)
                .onUnmappableCharacter(CodingErrorAction.REPLACE);
                //看似高深,其實這是為物件賦值的另一種形式,也可稱為工廠模式,就相當於set,是底層眾多初始化物件的其中一種手段
                //每個方法都是返回自己本身,這樣賦值之後就可以繼續調其方法。好處,就是簡單明瞭。
                //在建立之初就可以直接賦值,並且後期維護時可以非常簡單。可以直接看到到底初始化了哪些資料
            this.isTrusted = (cs.getClass().getClassLoader0() == null);//載入class
}

(1)②

byte[] encode(char[] ca, int off, int len) {
            int en = scale(len, ce.maxBytesPerChar());//簡單的東西就不展現程式碼了,這一步僅僅是兩個引數相乘。
            //第一個引數是字串的長度,第二個是每個字元生成的最大位元組數,得到的就是該字串的最大位元組數的長度
            byte[] ba = new byte[en];//建立一個這麼長的byte陣列
            if (len == 0)
                return ba;//如果長度為0  就不存在轉碼,直接返回
            if (ce instanceof ArrayEncoder) {
                int blen = ((ArrayEncoder)ce).encode(ca, off, len, ba);
                return safeTrim(ba, blen, cs, isTrusted);//如果為true,就跳出了jdk,很坑
                //分析一下吧:ce是CharsetEncoder抽象類,ArrayEncoder是一個介面,相互之間沒有介面或者繼承關係,
                //應該是  與本次邏輯無關的一部 判斷,大概是 其他地方呼叫會用到,因為應該是 進不來這一步
                //看不到ArrayEncoder的原始碼,只能知道這麼多了
            } else {
                ce.reset();//對CharsetEncoder的一步初始化操作,只是設定他的屬性state為0
                ByteBuffer bb = ByteBuffer.wrap(ba);//byte型別資料讀取緩衝區,專案沒用到過,所以具體解釋不清
                //不過字面意思很好理解,就是一個提供緩衝的類
                CharBuffer cb = CharBuffer.wrap(ca, off, len);//顧名思義,char型別緩衝區
                try {
                    CoderResult cr = ce.encode(cb, bb, true);①
                    if (!cr.isUnderflow())
                        cr.throwException();
                    cr = ce.flush(bb);
                    if (!cr.isUnderflow())
                        cr.throwException();
                } catch (CharacterCodingException x) {
                    // Substitution is always enabled,
                    // so this shouldn't happen
                    throw new Error(x);
                }
                return safeTrim(ba, bb.position(), cs, isTrusted);
            }
}
(1)②①
public final CoderResult encode(CharBuffer in, ByteBuffer out,
                                    boolean endOfInput){
                                    //endOfinput  傳的固定的true
                                    //另外兩個就是   把資料扔進換成 然後帶過來
        int newState = endOfInput ? ST_END : ST_CODING;
        //ST_END為2,ST_CODING為3:前面的為true,所以   newState為2
        if ((state != ST_RESET) && (state != ST_CODING)
            && !(endOfInput && (state == ST_END)))
            throwIllegalStateException(state, newState);
        state = newState;//state由0變為2

        for (;;) {//傾向於c語言的習慣。題外話:其實jdk的程式碼規範也沒那麼嚴謹,依舊能看出來,誰是誰寫的程式碼
	//就是普通的for迴圈,從編譯的角度講,while的程式碼體積要大一點點,所以for要好一點點
	//程式碼規範其實和程式碼效率並沒有那麼的貼合,儘管別的國家的程式碼規範實施的很好,但是中國程式設計師寫的程式碼顯然比外國
	//效率是要高的。首先外國寫的專案幾乎沒什麼高併發,可在中國幾千幾萬還算是正常的,一般程式設計師都能應付
            CoderResult cr;
            try {
                cr = encodeLoop(in, out);//編碼迴圈,c語言實現的,返回的類,是c對記憶體地址資料處理後對產生結果的總結。
            } catch (BufferUnderflowException x) {
                throw new CoderMalfunctionError(x);
            } catch (BufferOverflowException x) {
                throw new CoderMalfunctionError(x);
            }

            if (cr.isOverflow())
                return cr;

            if (cr.isUnderflow()) {
                if (endOfInput && in.hasRemaining()) {
                    cr = CoderResult.malformedForLength(in.remaining());
                    // Fall through to malformed-input case
                } else {
                    return cr;
                }
            }

            CodingErrorAction action = null;
            if (cr.isMalformed())
                action = malformedInputAction;
            else if (cr.isUnmappable())
                action = unmappableCharacterAction;
            else
                assert false : cr.toString();

            if (action == CodingErrorAction.REPORT)
                return cr;

            if (action == CodingErrorAction.REPLACE) {
                if (out.remaining() < replacement.length)
                    return CoderResult.OVERFLOW;
                out.put(replacement);
            }

            if ((action == CodingErrorAction.IGNORE)
                || (action == CodingErrorAction.REPLACE)) {
                // Skip erroneous input either way
                in.position(in.position() + cr.length());
                continue;
            }

            assert false;
            //斷言,執行的此處就肯定是錯誤的,也是意料之外發生的事情,但是肯定不是預期的,是錯的,斷言拋異常
            //一些列12345的各種判斷,沒寫備註,無法搞清楚在判斷啥,但也算是到最底層了。這個getByte終於結束了
        }

}

2018年11月14日 17:26:00,第一次更新1-12
2018年11月15日 11:56:25,第二次更新13-17
2018年11月17日 17:29:38,第三次更新18-23