Android研發中對String的思考(源代碼分析)



一、常用建立方式思考:

String text = "this is a test text ";

上面這一句話其實是運行了三件事 
一、聲明變量 String text;
二、在內存中開闢空間 (內存空間一)
三、將變量的text的引用指向開闢的內存空間java

當有 git

text = "this is a change text";

這一句話運行了兩件事
一、在內存中開闢空間
二、將變量text 的引用指向 新開闢的內存空間
三、內存空間一此時依舊存在,這就是說明了String的不可變性


測試實踐一:
小程序

String text = "oo";
 //循環體系將字符串拼接
for (int i = 0; i < 90000; i++) {
      text+=i;
}

這段小程序在個人應用中運行了8s的時間 ,太長了,緣由很是easy。就是不斷的在反覆建立新的對象與內存空間。同一時候還要不時的釋放未使用的內存空間api

    

測試實踐二:
數組

String text = "oo";
//建立字符緩衝區
StringBuilder builder = new StringBuilder(text);
//循環體系將字符串拼接
for (int i = 0; i < 100000; i++) {
      builder.append(i);
 }
運行這一小段代碼,運行的次數遠遠超於實踐一中。然而時間僅僅使用了 30 ms,大優化了性能,緣由僅僅是因爲其在僅僅是開闢了一個緩存空間,每次僅僅把新的數據加入到緩存中。


二、建立String 的構造簡析

2.一、String string = new String ();

這個方案是建立了一個空的String,在內存空間中開闢了一個空內容的地址,實際上也沒有什麼用,源代碼中有這種描寫敘述 緩存

/**
     * Initializes a newly created {@code String} object so that it represents
     * an empty character sequence.  Note that use of this constructor is
     * unnecessary since Strings are immutable.
     */
    public String() {
        this.value = "".value;
    }

儘管是空的String(是""而不是null。「」出是佔有內存空間,僅僅只是內容是空的) 。但在其構造方法中依舊調用了value方法。也就是空字符串的value,
所謂value  

 /** The value is used for character storage. */
    private final char value[];


2.二、String string = new String (" test "); 

這樣的方法來建立String,與咱們常用建立方式分析中的思路一至,聲明變量,開闢空間。賦值引用 安全

源代碼中這樣操做app

public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

  毫無疑問,它的構造方法中首先將其轉爲了相應的字節數組中的數據。
同一時候 獲取其相應的Hash值 

 /** Cache the hash code for the string */
    private int hash; // Default to 0

2.三、將char數組中的內容構造爲String類型

 char[] chars = new char[]{"q","e","e","q","e","e"};
        //將char數組中的內容構造爲String類型
        String string = new String(chars);

        //構造的字符串爲 qeeqee

這裏是依賴字符數組來建立構造String  。從源代碼的角度來看,毫無疑問在其相應的構造中首先將其轉爲了相應字符數組 

 public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }
可以看到這裏並無直接經過 Arrays的copyOf建立了一個新的字符數組,並賦值 於this.value,
public static char[] copyOf(char[] original, int newLength) {
            char[] copy = new char[newLength];
            System.arraycopy(original, 0, copy, 0,
                            Math.min(original.length, newLength));
            return copy;
        }

2.四、 將字符數組中的部分數據構造爲String 

 char[] chars = new char[]{"q","e","e","q","e","e"};
        //構造字符串
        String string = new String(chars,0,2);

        //構造的字符串爲 qe 
        //也就是說將字符數組中的部分字符 構形成String 

從其構造源代碼來看 :

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

也就是說當咱們經過這樣的方法來構造String 的時候 , 傳入的參數中構造字符起始位置 offset小於零。那麼將會出異常,構造字符個數 count 小於0,ide

那麼意味取出的字符數組中的長度爲0,也就是構造一個 ""字符串,同一時候賦值其字符數組。post


當構造起始位置 字符個婁都不小於0的時候 ,當起始位置與取出的字符長度設置不合理(所謂不合理指的是 例 字符數組長度爲 5,

構造字符串的時候傳入的 起始位置 爲3 。構造長度爲5,也就是說在字符數組中從3號位置取值,向後延取5個值,原字符數組長度一定不夠)的時候,

拋出異常


當傳入的參數合理的時候。則經過Arrays的copyOfRange方法建立一個新的字符數組空間,並賦值 this.value 

public static char[] copyOfRange(char[] original, int from, int to) {
            int newLength = to - from;
            if (newLength < 0)
                throw new IllegalArgumentException(from + " > " + to);
            char[] copy = new char[newLength];
            System.arraycopy(original, from, copy, 0,
                            Math.min(original.length - from, newLength));
            return copy;
        }

2.六、 經過 unicode數組來構造String

int[] charIntArray = new int[]{67,68,69,70};

String  string = new String (charIntArray,0,charIntArray.length);

//相應的字符串 CEDF 

從源代碼角度來看:(詳細的關於unicode將在下文中簡述)

public String(int[] codePoints, int offset, int count) {
                if (offset < 0) {
                    throw new StringIndexOutOfBoundsException(offset);
                }
                if (count <= 0) {
                    if (count < 0) {
                        throw new StringIndexOutOfBoundsException(count);
                    }
                    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;

                // Pass 1: Compute precise size of char[]
                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));
                }

                // Pass 2: Allocate and fill in char[]
                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;
            }

2.七、將字節數組構建爲String

byte[] newByte = new byte[]{4,5,6,34,43};
        //構造字符 
        String string = new String (newByte,0,newByte,"UTF-8");
        //參數一 相應的字節數組
        //參數二 參數三 構造字符串的字節範圍
        //參數四 構造字符串的編碼方式 這裏使用的 爲UTF - 8;

從其源代碼的角度來看

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);
            }

假設傳入的編碼方式爲空。則直接拋出異常

而如下的checkBounds方法僅僅是作了一些安全性檢驗 

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);
            }

  而後經過 方法StringCoding.decode 方法建立了一個新的字符數組,並賦值與 this.value 

2.八、 將字節數組中的一部分數據構建爲String

        byte[] newByte = new byte[]{4,5,6,34,43};
        //構造字符 
        String string = new String (newByte,"UTF-8");

三、常用處理String操做的方法分析

3.一、 取出一個String中的指定角標下的一個字符 

 String text = "thisisapicture";
        //取出角標索引爲0位置的字符
        char cahrString = text . charAt(0);

        //這裏取出來的字符爲 t 

從源代碼角度來看

public char charAt(int index) {
            if ((index < 0) || (index >= value.length)) {
                throw new StringIndexOutOfBoundsException(index);
            }
            return value[index];
        }

從其源代碼看來,當咱們剛剛建立一個String 對象的時候,String對象的內容則會被轉爲內存空間中的對應的字數組,而在這裏,

則是從其相應的內存數組中拿到相應角標下的相應字符 


3.二、 對字符串的切割 


        3.2.一、 split方法進行切割

 String textString = "q,q,w,e,e,r,f,g3,g";
        //將字符串以「,」爲基準進行切割
        String[] stringArray = textString.split(",");
        //獲得對應的字符串數組 
        // [q, q, w, e, e, r, f, g3, g]

從源代碼的角度來看 

public String[] split(String regex) {
            return split(regex, 0);
        }
實際中調用了兩個參數的重載方法

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; 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); }


當看到這段源代碼的時候。有點頭疼,因爲它有點長。事實上細緻一看,也只是如此

可以看到方法內有一個if,假設條件爲true。那麼就使用indexOf()打開循環體系,推斷後substring()截取,
         //第一步部分:當regex的長度爲1且不是「.$|()[{^?*+\\」中的時,爲真
            (regex.value.length == 1 &&".$|()[{^?

*+\\".indexOf(ch = regex.charAt(0)) == -1)
        //第二部分:當長度爲2時且第一個字符爲「\」轉義字符。第二個字符不是字符0-9 a-z A-Z 以及utf-16之間的字符
       

從if可以看出假設regex內容爲一個非正則匹配符或者是轉之後的特殊字符時,採用indexOf()+substring()處理。

不然使用正則表達式   Pattern.compile(regex).split(this, limit)

        也就是說,咱們在使用Split方法對String字符串進行切割的時候,不只可以以一個普通的字符爲標準進行切割,

還可以使用一個正則表達式進行切割

        而當使用的切割符號正好爲正則表達式中的某些符號的時候。需要正則轉義才能夠獲得正確的結果 


3.3 獲取字符串中指定角標字符的Unicode編碼 

String text = "ABCD";
    //獲取0號位置也就是'A'字符的編碼
    int uniCode = text.codePointAt(0);

從源代碼來看 :

public int codePointAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointAtImpl(value, index, value.length);
    }

實際的操做體則是Character這個類在發揮做用:Character.codePointAtImpl(value, index, value.length)

也就是直接將String text相應的字符數組。以及要獲取字符角標,以及字符數組長度傳參

        Unicode給世界上每個字符分配了一個編號。編號範圍從0x000000到0x10FFFF。

                編號範圍在0x0000到0xFFFF之間的字符,爲常用字符集,稱BMP(Basic Multilingual Plane)字符。


                編號範圍在0x10000到0x10FFFF之間的字符叫作增補字符(supplementary character)。

        Unicode主要規定了編號。但沒有規定假設把編號映射爲二進制,UTF-16是一種編碼方式,或者叫映射方式,

它將編號映射爲兩個或四個字節,對BMP字符。它直接用兩個字節表示,

對於增補字符,使用四個字節,前兩個字節叫高代理項(high surrogate)。範圍從0xD800到0xDBFF,

後兩個字節叫低代理項(low surrogate),範圍從0xDC00到0xDFFF,UTF-16定義了一個公式。可以將編號與四字節表示進行相互轉換。

    Java內部採用UTF-16編碼。char表示一個字符。但僅僅能表示BMP中的字符,對於增補字符,需要使用兩個char表示。一個表示高代理項。一個表示低代理項。

    使用int可以表示隨意一個Unicode字符。低21位表示Unicode編號,高11位設爲0。整數編號在Unicode中通常稱爲代碼點(Code Point),表示一個Unicode字符,與之相對,另外一個詞代碼單元(Code Unit)表示一個char。

    而在Character這個類中,相應的靜態操做方法實則爲:

static int codePointAtImpl(char[] a, int index, int limit) {
        char c1 = a[index];
        if (isHighSurrogate(c1) && ++index < limit) {
            char c2 = a[index];
            if (isLowSurrogate(c2)) {
                return toCodePoint(c1, c2);
            }
        }
        return c1;
    }
實際 在這種方法中的第一步是從text相應的字符數組中取出相應的角標的字符,假設不符合當中if的條件,那麼將直接獲取的是字符自己。

也就是字符自己的unicode編碼是自己
    在if的推斷條件中.運行推斷取出的字符是否在isHighSurrogate這種方法所限定的範圍內,

public static boolean isHighSurrogate(char ch) {
        // Help VM constant-fold; MAX_HIGH_SURROGATE + 1 == MIN_LOW_SURROGATE
        return ch >= MIN_HIGH_SURROGATE && ch < (MAX_HIGH_SURROGATE + 1);
    }
MIN_HIGH_SURROGATE utf-16 編碼中的 unicode 高代理項代碼單元的最小值。高代理項也稱爲前導代理項。
    MAX_HIGH_SURROGATE utf-16 編碼中的 unicode 高代理項代碼單元的最大值


假設在。則運行 isLowSurrogate方法的限定推斷 

public static boolean isLowSurrogate(char ch) {
        return ch >= MIN_LOW_SURROGATE && ch < (MAX_LOW_SURROGATE + 1);
    }

此方法可以理解爲 肯定給定char值是否爲一個Unicode低代理項代碼單元
    也可以理解爲 推斷相應字符是否在  0xDC00到0xDFFF 範圍的 低代理項。
    假設 在,則運行toCodePoint方法計算其相應的Unicode值,也就是依據高代理項high和低代理項low生成代碼單元

public static int toCodePoint(char high, char low) {
        // Optimized form of:
        // return ((high - MIN_HIGH_SURROGATE) << 10)
        //         + (low - MIN_LOW_SURROGATE)
        //         + MIN_SUPPLEMENTARY_CODE_POINT;
        return ((high << 10) + low) + (MIN_SUPPLEMENTARY_CODE_POINT
                                       - (MIN_HIGH_SURROGATE << 10)
                                       - MIN_LOW_SURROGATE);
    }


3.四、獲取字符串中指定角標索引前一個元素的代碼點

String textString = "ABCDEF";
        //這裏是獲取三號位置前面一位,也就是二號位 C 的uniCode編碼 
        int uniCode = textString.codePointBefore(3);
從源代碼來看:實際實操做的仍是字符串相應的字符數組,操做方法依舊是Character這個類所定義的靜態方法

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);
        }


而在Character這個類中的方法中則是直接從相應的字符數組中取出角標前一位的字符,

而後斷定是否在編碼區中的 高 低代理區,假設 在。那麼就計算返回相應的編碼

static int codePointBeforeImpl(char[] a, int index, int start) {
        char c2 = a[--index];
        if (isLowSurrogate(c2) && index > start) {
            char c1 = a[--index];
            if (isHighSurrogate(c1)) {
                return toCodePoint(c1, c2);
            }
        }
        return c2;
        }


3.五、獲取字符串中指定範圍內的字符串Unicode代碼點數

String textString = "ABCDEF";
        //這裏獲取的是整個字符串所相應的 代碼點數
        int uniCodeCount = textString.codePointCount(0, textString.length());
從源代碼角度來看 實際仍是在操做String相應的字符數組。負責操做的是 Character這個類

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);
        }
這裏直接傳入字符串相應的字符數組,以及範圍的開始點,範圍長度 

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])) {
                    n--;
                    i++;
                }
            }
                return n;
        }
 可以看出來 在這裏面也是一個循環推斷每一個字符是否在高代理項區與低代理項區,而後進行計數



3.六、比較兩個字符串是否相等 

        String text1 = "ABCDEF";
        String text2 = "ABCDEFG";
        //比較
        int compareNumber = text1.compareTo(text2);
事實上就是依次比較兩個字符串ASC碼。

假設兩個字符的ASC碼相等則繼續興許比較,不然直接返回兩個ASC的差值。

假設兩個字符串全然同樣。則返回0

從源代碼角度來看:

 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;
        }

在這裏先字符串自己相應的字符數組的長度,而後再經過 方法anotherString.value.length方法獲取比較對象String的長度


3.7 忽略大寫和小寫比較兩個字符串

String text1 = "ABCDEF";
        String text2 = "ABCDEFG";
        //比較
        int compareNumber = text1.compareToIgnoreCase(text2);


從源代碼角度來看 

public int compareToIgnoreCase(String str) {
            return CASE_INSENSITIVE_ORDER.compare(this, str);
        }

 這裏直接使用了CASE_INSENSITIVE_ORDER的compare方法來比較兩個字符串。

而CASE_INSENSITIVE_ORDER是String類中定義的一個比較器
        (1.2版本號開始使用)
        public static final Comparator<String> CASE_INSENSITIVE_ORDER



3.8 將兩個字符串拼接爲一個字符串

 String text1 = "ABCDEF";
        String text2 = "ABCDEFG";
        //這裏將 text2 拼接到text1後面 
        String newString  = text1.concat(text2);

     將兩個字符串拼接到一塊兒,可以使用 text1+text2這種方法,僅僅只是是在性能上有一點點的不合適 

      從源代碼角度來看

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);
        }

  第一步 在這裏先進行字符串斷定,假設要拼接的字符串是"",那麼拼接後依舊是自己,所在這裏直接return。
        第二步 經過 Arrays.copyOf方法來創新一個新的指定大小的數組

 public static char[] copyOf(char[] original, int newLength) {
                char[] copy = new char[newLength];
                System.arraycopy(original, 0, copy, 0,
                                 Math.min(original.length, newLength));
                return copy;
            }
            //這裏建立一個新長度的數組空間並將原來的字符數組COPY進去


第三步 經過 String 的 getChars方法將 兩個字符串相應的字符數組拼接
        第四步 構造新的字符串返回

3.9 查找一個字符在字符串中第一次出現的位置 

 String text = "ABCD ";
        //查找 "A" 在字符串text中第一次出現的位置 
        int index = text.indexOf("A");

從源代碼角度來看

 public int indexOf(String str) {
            return indexOf(str, 0);
        }

        //再看indexOf(String str, int fromIndex)方法

 public int indexOf(String str, int fromIndex) {
         return indexOf(value, 0, value.length,
                    str.value, 0, str.value.length, fromIndex);
        }


        上述indexfOf(String str)方法中調用indexOf(String str,int fromIndex)方法,而後傳入0,也就是說將從String的開始位置查找指定字符在字符串中出現的位置,

        

而在indexOf(String str,int fromIndex)這種方法中則調用了indexOf的多參數方法,這時分別傳入 
            value   父字符串的字符數組
            0       父字符串的有效字符起始角標索引 這裏傳入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;
        }


在這種方法中,參數最多,那麼說明處理的方法就在這裏面 
先分析參數 
            char[] source,         父String 相應字符數組 
            int sourceOffset,      父String 被使用起始索引
            int sourceCount,       父String 相應字符數組長度
            char[] target,         子String 相應字符數組
            int targetOffset,      子String 被使用超始索引
            int targetCount,       子String 相應字符數組長度
            int fromIndex           檢索起始位置 

        從上到下 
            if (fromIndex >= sourceCount) {
                    return (targetCount == 0 ? sourceCount : -1);
                }
            假設查找子String標識的在父View中的起始位置 大於等於 父String的長度
            則終止操做。返回0 或者 -1 
            當子字符串的長度爲0的時候。則返加0,此時子字符串爲」「 內容爲空的空字符串
            當子字符串的長度不爲0的時候,返回-1,也就是說沒有查找到


            if (fromIndex < 0) {
                fromIndex = 0;
            }  
            當檢索起始位置小於0,將起始位置默認設爲0

            if (targetCount == 0) {
                return fromIndex;
            }
            當檢索的子String相應的字符數組長度爲0時。返回傳入的檢索位置 

            char first = target[targetOffset];
            取出檢索子String的第一個字符 

            int max = sourceOffset + (sourceCount - targetCount);
            父String被檢索起始位 + (父String相應字符數組長度 - 子String相應字符數組長度)

            for (int i = sourceOffset + fromIndex; i <= max; i++) {
                。。

。。
            }
            假設循環體系中的條件沒有被知足。那說明沒有查找到相應的字符,返回-1 

            在循環體系中 
             /* 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;
                    }
                }


3.10 推斷一個字符串中是否包括指定的某一個字符 

String text = "ABCE";
        //推斷text中是否包括 "A"
        boolean isContans = text.contains("A");


從源代碼的角度來看:

public boolean contains(CharSequence s) {
            return indexOf(s.toString()) > -1;
        }

可以看到事實上實際中仍是String 的indexOf在發揮實際的做用,這裏調用的indexOf(String str)方法,當查找到相應的字符時,會返回這個字符在字符串中的角標索引,角標索引從0 開始 確定大於-1,與-1相比較,返回true
        假設沒有找到,indexOf方法返回的是 -1,終於contains方法返回的是 false,假設傳入的是 ""空字符串。indexOf方法會返回0,終於contains方法返回的是true 

3.11 比較字符串 到指定的CharSequence 序列是否一樣 

(2016-9-22 18:43 更新)

 String text1 = "ABCDEF";
        //比較序列
        boolean isExit = text1.contentEquals("AABCDEF");

從源代碼的角度來看
        public boolean contentEquals(StringBuffer sb) {
            return contentEquals((CharSequence)sb);
        }
        public boolean contentEquals(CharSequence cs) {
            // Argument is a StringBuffer, StringBuilder
            if (cs instanceof AbstractStringBuilder) {
                if (cs instanceof StringBuffer) {
                    synchronized(cs) {
                       return nonSyncContentEquals((AbstractStringBuilder)cs);
                    }
                } else {
                    return nonSyncContentEquals((AbstractStringBuilder)cs);
                }
            }
            // Argument is a String
            if (cs instanceof String) {
                return equals(cs);
            }
            // Argument is a generic CharSequence
            char v1[] = value;
            int n = v1.length;
            if (n != cs.length()) {
                return false;
            }
            for (int i = 0; i < n; i++) {
                if (v1[i] != cs.charAt(i)) {
                    return false;
                }
            }
            return true;
        }

這裏兩個方法重載,傳類型不同。CharSequence類型和StringBuffer類型

而實際操做起做用的是contentEquals(CharSequence cs)方法

而在這種方法中

if (cs instanceof AbstractStringBuilder) {
                     if (cs instanceof StringBuffer) {
                          synchronized(cs) {
                            return nonSyncContentEquals((AbstractStringBuilder)cs);
                       }
            } else {
                 return nonSyncContentEquals((AbstractStringBuilder)cs);
          }
    }
可以看出來,當咱們傳入的參數 cs 是AbstractStringBuilder的實例的時候 運行if內部的方法並且 假設是StringBuffer的實例,加同步鎖,

而在方法nonSyncContentEquals中

private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
                    char v1[] = value;
                    char v2[] = sb.getValue();
                    int n = v1.length;
                    if (n != sb.length()) {
                        return false;
                    }
                    for (int i = 0; i < n; i++) {
                        if (v1[i] != v2[i]) {
                            return false;
                        }
                    }
                    return true;
                }

這種方法中吧,感受到無語。首先先獲取參數的相應字符數組的長度,而後比較字符數組的長度,假設長度不一樣樣,那麼直接返回false,也就是這兩個字符串的序列號確定不同,
                假設長度同樣,再一循環比較每一個字符

                假設不知足if中的推斷語句後。在向下運行,

// Argument is a String
                if (cs instanceof String) {
                    return equals(cs);
                }


  假設傳參是String的實例的時候 
                則調用了String的equals方法進行比較

                假設不知足上面的條件。再向下運行

 // Argument is a generic CharSequence
                    char v1[] = value;
                    int n = v1.length;
                    if (n != cs.length()) {
                        return false;
                    }
  可以看到這一步是直接比較 的字符參數的長度 ,假設長度不同,那麼其相應的序列號確定也就不同了
                假設不知足if條件
   for (int i = 0; i < n; i++) {
                        if (v1[i] != cs.charAt(i)) {
                            return false;
                        }
                    }

  可以看到繼續向下。則是比較每一個字符,不一樣樣返回false。
                假設不知足上述方法。則最後返回true


3.12 感受無力的 copyValueOf方法

(2016-9-22 18:43 更新)


            
        String string = "4444";
        
        char[] charTest = new char[]{'d','r','w','q'};
        
        String newString = string.copyValueOf(charTest);


構建結果盡然是: drwq 

        不忍心看源代碼,但是還得看下,一看。更是無語

         /**
         * Equivalent to {@link #valueOf(char[])}.
         *
         * @param   data   the character array.
         * @return  a {@code String} that contains the characters of the
         *          character array.
         */
        public static String copyValueOf(char data[]) {
            return new String(data);
        }

擦,盡然是調用了String的一個構造來構造了一個新的 String 

3.13 推斷字符串是否以指定的字符或者字符串開頭

(2016-9-23 8:33 更新)

 String string = "andThisIs4";
        //推斷是否以a開頭
        boolean flag = string.startsWith("a");
        //輸出結果爲 true 

從源代碼角度來看:
        public boolean startsWith(String prefix) {
            return startsWith(prefix, 0);
        }

這裏調用其重載方法
        對於這種方法來講,傳入兩個參數 
        String string = "thisIsAndAnd";
        //推斷從二號索引位置 是不是以this開頭
        boolean flag = string.startsWith("this",2);
        //結果 爲 flase 

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;
            // Note: toffset might be near -1>>>1.
            if ((toffset < 0) || (toffset > value.length - pc)) {
                return false;
            }
            while (--pc >= 0) {
                if (ta[to++] != pa[po++]) {
                    return false;
                }
            }
            return true;
        }

從上到下 :
         //獲取 父String 相應的字符數組 
         char ta[] = value;
         //賦值 檢索父String的起始位置 
            int to = toffset; 
         //獲取 子String 相應的字符數組 
            char pa[] = prefix.value;
         //定義變量標識
            int po = 0;
         //獲取 子String 相應字符數組的長度
            int pc = prefix.value.length;
         //假設檢索 父String的位置 小於0 返回false
         //假設 
            if ((toffset < 0) || (toffset > value.length - pc)) {
                return false;
            }
          //循環比較
            while (--pc >= 0) {
                if (ta[to++] != pa[po++]) {
                    return false;
                }
            }
            return true


3.14 推斷字符串是否以指定的字符或者字符串結尾

(2016-9-23 8:33 更新)

String string = "andrThisIs4";
        //推斷是不是以 4 結尾
        boolean flag = string.endsWith("4");
        //結果 爲true


從源代碼角度來看 

public boolean endsWith(String suffix) {
                return startsWith(suffix, value.length - suffix.value.length);
            }

不忍直視。它盡然調用了 startsWith方法來運行推斷

3.15 比較兩個字符串中的內容是否一樣

(2016-9-23 8:33 更新)

        String text1 = "ABCD";
        String text2 = "ABCE";
        //比較兩個字符串是否相等
        boolean flage = text1.equals(text2);


text1 text2 分別指向兩個堆內存空間 
        當使用 text1 == text2 來推斷時,比較的是text1 與 text2這兩個變量 相應的引用地址是否相等。也就是說其指向的內存地址是否同樣 

        從源代碼角度來看 equals方法

public boolean equals(Object anObject) {
             if (this == anObject) {
                    return true;
                }
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                int n = value.length;
                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;
        }



        參數格式要求是 Object類型


        在這種方法中,其先比較的兩個變量指向的堆內存空間地址是不是同樣,假設同樣,那麼直接返回true ,
            當 String text1 = "ABC";在內存空間開闢空間,並賦值變量text1
            當定義 Sring text2 ="ABC";時。虛擬機會先去內存空間的常量區中尋找是否有相應內容的空間,假設有。那麼就直接指向那邊,在這裏,因建立的text1 內容與 text2的內容一致,因此在text2建立的時候,會將text2的引用指向直接指向text1指向的內存空間


        在接下來的一步中。假設傳入的比較參數是String實例。將其強轉爲String 類型
        而後經過循環體制 一一比較每一個字符,


3.16 獲取String默認格式下的字節編碼

(2016-9-27 14:33 更新)


        String text1 = "ABCDEF";
        //獲取默認編碼下的字節數組 
        byte[] bytes = text1.getBytes();

從源代碼角度來看:

        public byte[] getBytes() {
            return StringCoding.encode(value, 0, value.length);
        }
這裏間接使用了StringCoding類的靜態方法 encode方法

        static byte[] encode(char[] ca, int off, int len) {
            String csn = Charset.defaultCharset().name();
            try {
                // use charset name encode() variant which provides caching.
                return encode(csn, ca, off, len);
            } catch (UnsupportedEncodingException x) {
                warnUnsupportedCharset(csn);
            }
            try {
                return encode("ISO-8859-1", ca, off, len);
            } catch (UnsupportedEncodingException x) {
                // If this code is hit during VM initialization, MessageUtils is
                // the only way we will be able to get any kind of error message.
                MessageUtils.err("ISO-8859-1 charset not available: "
                                 + x.toString());
                // If we can not find ISO-8859-1 (a required encoding) then things
                // are seriously wrong with the installation.
                System.exit(1);
                return null;
            }
        }

在這種方法中首先經過 String csn = Charset.defaultCharset().name(); 獲取默認的編碼方式(「UTF-8」)
        而後調用其重載的方法來進行編譯 ,
        假設出現異常。則使用 ISO-8859-1 的編碼方式來進行解析,假設再出現異常,系統異常退出

3.17 獲取String指定編碼格式下的字節數組 

(2016-9-27 14:33 更新)

        String text1 = "abef";
        byte[] bytes = null;
        
        try {
            bytes = text1.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
可見這種方法是獲取默認編碼格式下的字節數組方法的重載方法

        從源代碼角度來看

        public byte[] getBytes(String charsetName)
            throws UnsupportedEncodingException {
                if (charsetName == null) throw new NullPointerException();
                return StringCoding.encode(charsetName, value, 0, value.length);
        }
可以看到這裏直接調用的是 StringCoding 的四個參數的重載方法(getBytes()方法調用的是三個參數的encode方法)
 

3.18  將String 中的部分字符拷貝到指定的字符數組中去

(2016-9-27 14:33 更新)

        String text1  ="ABCDEF";
        //目標字符數組 
        char[] chars = new char[10];
        //拷貝
        text1.getChars(0,text1.length(),chars,0);
        //參數一 參數二  拷貝String中字符的範圍
        //參數三  目標字符數組
        //參數四  目標字符數組中的存儲起始位置

從源代碼角度來看 

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);
        }


其直接調用 System 的拷貝數組的方法將數據拷貝

        public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
對於System的arraycopy方法來講,其直接調用 JNI層方法實現


        在上述調用 System的arraycopy方法的時候 傳入參數 
                參數一  (value) 父級String相應的字符數組 
                參數二  (srcBegin) 父級String開始拷貝數據的起始位置 
                參數三  (dst)   目標數組
                參數四  (dstBegin) 目標數組存儲數據的起始位置 
                參數五  (srcEnd - srcBegin) 截取父String中字符的長度


3.19 將其它數據類型轉爲String類型(String的靜態方法valueOf()方法)

(2016-10-8 14:00 更新)

3.19.1

    //將boolean類型轉爲String類型
    String newString = String.valueOf(true);


源代碼中這樣描寫敘述

    public static String valueOf(boolean b) {
        return b ?

"true" : "false"; }

源代碼中這樣寫到,我可以說我也是有點醉了

3.19.2

    //將字符類型轉爲String類型
    
    char textChar = 'd';

    String newString = String.valueOf(textChar);
    
源代碼中這樣來描寫敘述:

    public static String valueOf(char c) {
        char data[] = {c};
        return new String(data, true);
    }
 
在valueOf方法中直接構造了一個char數組,而後再經過String的構造方法將char數組構形成爲一個新的String。只是在這裏使用的這個String構造讓我感到有點無語

   /*
    * Package private constructor which shares value array for speed.
    * this constructor is always expected to be called with share==true.
    * a separate constructor is needed because we already have a public
    * String(char[]) constructor that makes a copy of the given char[].
    */
    String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
    }

可以看獲得這是一個私有的方法,傳入的第二個參數沒有使用到

3.19.3

    //將int類型數據轉爲String類型
    int textInt = 2345;
    String newString = String.valueOf(textInt);

  
從源代碼中來看 :

    public static String valueOf(int i) {
        return Integer.toString(i);
    }


事實上是 Integer類發揮了實際操做的做用
也就可以推理到

3.19.4 

    將float類型數據轉爲String。其實是Float.toString(f),方法發揮做用
    源代碼中這樣描寫敘述 :
    public static String valueOf(float f) {
        return Float.toString(f);
    }
    //將double類型數據轉爲String類型
    public static String valueOf(double d) {
        return Double.toString(d);
    }
    //將long類型數據轉換爲String
    public static String valueOf(long l) {
        return Long.toString(l);
    }

也就是說其基本數據類型相應的對象類的toString方法在發揮着實際的操做做用


3.20 去掉字符串先後的空格(trim()方法)

(2016-10-9 14:00 更新)

    String text = "  abce  ";
    //去掉空格
    String newString = text.trim();
    //生成新的字符串 "abce"


在源代碼中:

    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;
    }

    基本實現思路是 定義 角標索引標識 int st, int len ,

    接下來就是兩個循環分別檢索記錄字符串的開頭空格的位置,與字符串的結束空格的位置,最後調用推斷邏輯。

    當st=0,len=value.length,說明字符串的關部與尾部沒有空格,直接返回自己,假設不爲上述的值。st>0,說明字符串開頭有空格,len<value.length,說明字符串結尾有空格,則調用substring方法對字符串進行截取

  

3.21 截取固定角標範圍的字符串 (substring()方法)

(2016-10-10 18:00 更新)


    String text = "dfdfabcef";
    //截取 "dfdf"
    String newString = text.substring(0,4);


    


從源代碼的角度來看

    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);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }
    

    3.21.1  對於方法substring(int beginIndex,int endIndex),傳入參數beginIndex(截取字符串的開始位置),endIndex(截取字符串的結束位置)


    3.21.2  在方法的內部就對這兩個參數的範圍先作了範圍推斷,beginIndex開始位置最小也是0吧,不可能有負位,endIndex最長也僅僅能等於父字符串的長度(value.length  (value是父String相應的字符數組))吧,不可能超出父字符串的長度而去截取


    3.21.3  而後截取的子字符串的長度應爲 int length = endIndex - beginIndex;也就是咱們所說的不包括頭包括尾

    3.21.4  假設子字符串的長度length <0 那麼說明 傳入的 開始位置與結束位置是不合理的

    3.21.5  而後最後 經過 String 的三個參數的構造方法構造了新的字符串返回


官方這樣簡述:




    String text = "abcdefg";
    //截取 "bcdefg"
    String newText = text.substring(1);

   

源代碼角度來看 


    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }


    可以看到 不論是 substring(int beginIndex,int endIndex)方法。仍是substring(int beginIndex)方法,
    終於發揮操做的是 String 的一個三參數的構造方法 String(char value[], int offset, int count)
    而後實質就是將一個字符數組 從指定的位置開始截取固定長度的字符從而構形成 爲一個新的String 





未完... 

相關文章
相關標籤/搜索