【源碼】String類、jdk1.6subString內存泄漏、字符串拼接幾種區別、

1、String類源碼

出於安全性考慮,字符串常常做爲網絡鏈接、數據庫鏈接等參數,不可變就能夠保證鏈接的安全性
 
String類實現了3個接口:
  1.實現了io流的Serializable接口,用於代表String類的對象可被序列化.String在實現了Serializable接口以後,因此支持序列化和反序列化支持。Java的序列化機制是經過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,若是相同就認爲是一致的,能夠進行反序列化,不然就會出現序列化版本不一致的異常(InvalidCastException).
  2.實現Comparable接口,用於代表String類的對象進行總體排序,定義的泛型爲String類,說明給Comparable的數據類型只能是String類
  3.實現CharSequence接口,用於代表char值得一個只讀的字符序列。此接口對許多不一樣種類的char序列提供統一的自讀訪問。

 

string的本質是char[ ]字符數組,String類只是封裝字符串的一些操做的,真是的字符串就是存在其下value這個字符數組中的。  java

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];    //字符數組
   //private final char[] value;

   //hash是String實例化的hashcode的一個緩存。字符串的不變性確保了hashcode的值一直是同樣的,在須要hashcode時,就不須要每次都計算,這樣會很高效。
    private int hash;   //Default to 0

    private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];

    public String() {
        this.value = "".value;  //或者能夠寫this.value = new char[0];構造裏只能初始化長度,不能賦值如: ={'A'};
    }

    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    
  //本質是持有一個靜態內部類,用於忽略大小寫得比較兩個字符串。 public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();

    private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable {

    public int compare(String s1, String s2) { int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1);  //比較時忽略大小寫,同一索引的下標先都轉爲大寫比較,再轉爲小寫比較一次 c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } private Object readResolve() { return CASE_INSENSITIVE_ORDER; } } }

  CASE_INSENSITIVE_ORDER 若是須要忽略大寫,比較時傳入此比較器便可,由於實現了Comparator比較器接口。【Comparator是比較器,實現Comparable的對象自身能夠直接使用比較正則表達式

    或者直接使用來持有這個內部類的公共的靜態變量 CASE_INSENSITIVE_ORDER,能夠簡單得用它來比較兩個String,這樣當要比較兩個String時能夠經過這個變量來調用。數據庫

  而且String類中提供的compareToIgnoreCase方法其實就是調用這個內部類裏面的方法實現的。數組

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

  經過一個String 內部一個static的內部類實現的,那麼爲何還要特意寫一個內部類呢,這樣其實就是爲了代碼複用,這樣在其餘狀況下也可使用這個static內部類。緩存

 

  由於String本質就是經過char[]實現的,能夠發現length(),isEmpty(),charAt()這些方法其實就是在內部調用數組的方法。安全

  public int length() {
        return value.length;
    }

    public boolean isEmpty() {
        return value.length == 0;
    }

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

  

  將String轉化成二進制:本質上是調用了StringCoding.encode()這個靜態方法。網絡

    public byte[] getBytes() {
        return StringCoding.encode(value, 0, value.length);
    }

 

  equals:app

  首先進行當前對象和要判斷的對象引用的是否是同一個對象(==判斷引用地址),若是是則返回true。接着判斷要判斷的對象是否是String類的實例,若是是,接着轉化類型判斷兩個字符串的長度,若是同樣,進行char數組[]的逐一比較。函數

public boolean equals(Object anObject) {
        if (this == anObject) {  //首先進行當前對象和要判斷的對象引用的是否是同一個對象 return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;  //判斷要判斷的對象是否是String類的實例 int n = value.length;
            if (n == anotherString.value.length) {  //若是兩個字符串長度同樣,那麼一一比較char[] 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;
    }

  

  hashCode:    ui

  就是在存儲數據計算hash地址的時候,咱們但願儘可能減小有一樣的hash地址。若是使用相同hash地址的數據過多,那麼這些數據所組成的hash鏈就更長,從而下降了查詢效率。使用31的緣由多是爲了更好的分配hash地址,而且31只佔用5bits。在Java中,整型數是32位的,也就是說最多有2^32= 4294967296個整數,將任意一個字符串,通過hashCode計算以後,獲得的整數應該在這4294967296數之中。那麼,最多有 4294967297個不一樣的字符串做hashCode以後,確定有兩個結果是同樣的。

  hashCode方法能夠保證相同的字符串具備相同的hash值。可是hash值相同並不必定是字符串的value值相同。

    public int hashCode() {
        int h = hash;  //初始值爲0 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;
    }

  jdk1.7 Switch選擇表達式對String支持原理:

  其實,jdk1.7並無新的指令來處理switch string,而是經過調用switch中string.hashCode(),將string轉換爲int類型的hash值。而後用這個Hash值來惟一標識着這個case

  當匹配的時候,首先調用這個字符串的hashCode()方法,獲取一個Hash值(int類型),用這個Hash值來匹配全部的case,若是沒有匹配成功,說明不存在;若是匹配成功了,接着會調用字符串的equals()方法進行匹配。由於hashCode相同,字符串的value不必定相同

 

 

  contentEqauls:

  主要是用來比較String和StringBuffer或者StringBuild的內容是否同樣。能夠看到傳入參數是CharSequence ,這也說明了StringBuffer和StringBuild一樣是實現了CharSequence。源碼中先判斷參數是從哪個類實例化來的,再根據不一樣的狀況採用不一樣的方案,不過其實大致都是採用上面那個for循環的方式來進行判斷兩字符串是否內容相同。

public boolean contentEquals(CharSequence cs) {
        // Argument is a StringBuffer, StringBuilder
        if (cs instanceof AbstractStringBuilder) {
            if (cs instanceof StringBuffer) {                 synchronized(cs) {  //若是是線程安全的StringBuffer,那麼會使用同步代碼鎖 return nonSyncContentEquals((AbstractStringBuilder)cs);
                }
            } else {  //若是是StringBuilder return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        }
        // Argument is a String  //若是被比較的只是Sting類型,那麼直接調用equals方法
        if (cs instanceof String) {
            return equals(cs);
        }
        // Argument is a generic CharSequence  //若是是一個字符序列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;
    }

  //字符串與字符串緩衝區比較
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; }

  

  compareTo:

  這個就是String對Comparable接口中方法的實現了。

  先經過比較兩個字符串的長度將最小的長度賦值給lim,接着將字符串賦值給兩個字符數組,在0~lim範圍內進行字符數組的逐一判斷,若是有一個不相等則返回兩個字符的ASCII碼的差值,若是循環結束都相等則返回兩個長度的差值。其核心就是那個while循環,經過從第一個開始比較每個字符,當遇到第一個較小的字符時,斷定該字符串小。

  注意:anotherString.value 不報錯是由於在本類中本類對象的引用可使用private變量

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

  

  startswith:判斷當前字符串是否以某一段其餘字符串開始的,和其餘字符串比較方法同樣,其實就是經過一個while來循環比較。

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

 

  contact:

  concat的做用是將str拼接到當前字符串後面,經過代碼也能夠看出其實就是建一個新的字符串。

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

 

   replace和replaceAll:

  都是所有替換匹配的字符,而replaceAll是經過正則表達式的方式,替換全部匹配的字符

  若是隻替換第一個匹配的字符,使用replaceFirst

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 replaceAll(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceAll(replacement);
    }

  

  trim:

  字符的比較實際上是比較ASCII碼值,而空字符對應的值是32,是最小的,比32小那麼就是空字符

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;  //根據新的沒有空字符的索引來截取原字符串
    }

 

  string.valueOf(int i)與Integer.toString(int i)本質上沒什麼區別:

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

Integer:
    public static String toString(int i) {
        if (i == Integer.MIN_VALUE)
            return "-2147483648";
        int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
        char[] buf = new char[size];
        getChars(i, size, buf);
        return new String(buf, true);
    }

 

2、jdk1.6的subString內存泄漏問題:

  在 JDK 1.6 中,java.lang.String 主要由3 部分組成:表明字符數組的value、偏移量offset和長度count

char[] value
offset 偏移 
count 長度

  字符串的實際內容由value、offset 和count 三者共同決定,而非value 一項。若是字符串value 數組包含100 個字符,而count 長度只有1 個字節,那麼這個String 實際上只有1 個字符,卻佔據了至少100 個字節,那剩餘的99 個就屬於泄漏的部分,它們不會被使用,不會被釋放,卻長期佔用內存,直到字符串自己被回收。能夠看到,str 的count 爲1,而它的實際取值爲字符串「0」,可是在value 的部分,卻包含了上萬個字節,在這個極端狀況中,本來只應該佔用1 個字節的String,卻佔用了上萬個字節,所以,能夠斷定爲內存泄漏。 

public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
    throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
    throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
    throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value);
}

String(int offset, int count, char value[]) {   //構造方法只是改變數組的偏移和長度,不是產生新的String對象,因此形成內存泄露 this.value = value; 
    this.offset = offset; 
    this.count = count; 
} 

構造方法只是改變數組的偏移和長度,不是產生新的String對象,引用的仍是原字符串,原字符串永遠不會被回收,因此形成內存泄露

 

而在jdk1.7以後:

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

 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);  //複製建立了一個新的字符數組
    }

能夠看到,在substring()的實現中,最終是使用了String 的構造函數,生成了一個新的String,不會形成內存泄露。 

 

3、字符串拼接

concat:鏈接string各個字符串,是建立新的String對象(字符數組),只能接受String

+:默認是java的String類的一種重載,將+後面的對象,轉換爲String類型,而後再進行字符串拼接,其實都是產生了一個新的對象,+能夠接其它類型

字符串拼接幾種方式的效率:+ < contact < StringBuffer < StringBuilder

String apple = "Apple,";
String fruit = apple + "Pear," + "Orange"

其實底層編譯器在執行上述代碼的時候會的自動引入 java.lang.StringBuilder 類,上面這個例子中,編譯器會建立一個 StringBuilder 對象,用來構造最終要生成的 String,併爲每個字符串調用一次 StringBuilder 中的 append() 方法,所以上述代碼一共執行了三次 append() 方法。最後調用 toString 生成最終的結果,並保存爲 fruit。

但能使用StringBuilder最好不要用 +,如再循環裏 += 鏈接會在每次循環自動建立一次StringBuilder對象,下降效率

相關文章
相關標籤/搜索