1. 程式人生 > >JDK原始碼學習筆記——String

JDK原始碼學習筆記——String

1、學習jdk原始碼,從以下幾個方面入手:

  類定義(繼承,實現介面等)

  全域性變數

  方法

  內部類

2、hashCode

  private int hash;   
  public int hashCode() { int h = hash; 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;   }

 

為什麼是31?

(1)計算hashcode值一般選質數

(2)太小的數計算的hashcode值衝突率高,太大的數乘法計算會溢位int範圍

(3)有以上兩點和實驗得 出:31, 33, 37, 39 ,41 作為乘子比較合適

(4)這幾個數字中31的乘法運算可以被優化:31 * i == (i << 5) - i

3、構造兩種:

  (1)直接將otherStr引用給this

  (2)陣列copy

4、String對“+”的支援

public static void main(String[] args) {
    String s1="a" + "b";// 編譯之後 String s1 = "ab";
    String s = "a";
    String s2= s+ "b";// 編譯之後 String s = (new StringBuilder(String.valueOf(s))).append("b").toString();
}

5、jdk1.7修改subString()

// jdk1.6
String(int offset, int count, char
value[]) { this.value = value; this.offset = offset; this.count = count; } // subString方法部分 return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value); // jdk1.7 public String(char value[], int offset, int count) { . . . this.value = Arrays.copyOfRange(value, offset, offset+count); } // subString方法部分 return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen);

jdk1.6的substring:

(1)直接將引用賦值,效能好,共享內部陣列節約記憶體

(2)由於原String的value是private final,可以保證安全性

(3)可能導致記憶體洩漏

String aLongString = "...a very long string..."; // 很長
String aPart = data.substring(2, 4);
return aPart;

假設從一個很長的字串中提取一小段內容:

當aLongString不再使用,aPart繼續使用時,

aLongString被回收,aLongString的value還被aPart的value引用,不能被回收

導致記憶體洩漏

6、程式設計技巧學習

/**
 * 先比較是否同一個物件
 * 先比較長度
 * 雖然程式碼寫的內容比較多,但是可以很大程度上提高比較的效率
 */
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;
}

 

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

 

/**
 * 三目運算子代替多個if
 */
public boolean equalsIgnoreCase(String anotherString) {
    return (this == anotherString) ? true
            : (anotherString != null)
            && (anotherString.value.length == value.length)
            && regionMatches(true, 0, anotherString, 0, value.length);
}

7、intern()

 (1)String s = new String("abc");建立個幾個物件

  類載入時建立"abc"放入常量池  第一個

  執行程式碼時new String()  第二個

 (2)String存入常量池方式:

   一,直接使用雙引號宣告出來的String物件,在類載入時會直接儲存在常量池中。

   二,如果不是用雙引號宣告的String物件,可以使用String提供的intern方法。

    intern 方法:

    如果常量池中存在當前字串, 就會直接返回當前字串。 如果常量池中沒有此字串, 會將此字串放入常量池中後,再返回。

 (3)jdk6 和 jdk7 下 intern 的區別

  在 JDK1.2 ~ JDK6 的實現中,HotSpot 使用永久代實現方法區

  JDK7+ 移除永久代  字串常量和類引用被移動到 Java Heap中

   jdk6 intern:如果常量池中存在當前字串, 就會直接返回當前字串。 如果常量池中沒有此字串, 會將此字串複製一份到方法區,放入方法區中常量池,再返回。

   jdk7 intern:如果常量池中存在當前字串, 就會直接返回當前字串。 如果常量池中沒有此字串, 會將此字串的引用儲存一份,放入堆中常量池,再返回。

舉例:

public static void main(String[] args) {
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);// jdk1.6-false jdk1.7-false

    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);// jdk1.6-false jdk1.7-true
}

public static void main(String[] args) {
    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2);// jdk1.6-false jdk1.7-false

    String s3 = new String("1") + new String("1");
    String s4 = "11";
    s3.intern();
    System.out.println(s3 == s4);// jdk1.6-false jdk1.7-false
}

 

不再過多解釋: (注:圖中綠色線條代表 string 物件的內容指向。 黑色線條代表地址指向)

 

 

 

 (4)應用舉例:

static final int MAX = 1000 * 10000;
static final String[] arr = new String[MAX];

public static void main(String[] args) throws Exception {
    Integer[] DB_DATA = new Integer[10];
    Random random = new Random(10 * 10000);
    for (int i = 0; i < DB_DATA.length; i++) {
        DB_DATA[i] = random.nextInt();
    }
    long t = System.currentTimeMillis();
    for (int i = 0; i < MAX; i++) {
         arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();
    }

    System.out.println((System.currentTimeMillis() - t) + "ms");
    System.gc();
}

 

8、private final char[] value;final--->String的長度是不能改變的

參考 為什麼Java要把字串設計成不可變的

參考 java中String類為什麼要設計成不可變的

(1)常量池高效,常量池裡String物件改變,引用受影響

(2)安全,不可變,只能讀不能寫,保證執行緒安全

 

 

 

參考資料:

1、《成神之路-基礎篇》Java基礎知識——String相關

2、科普:為什麼 String hashCode 方法選擇數字31作為乘子

3、Java7為什麼要修改substring的實現

4、深入解析String#intern