1. 程式人生 > >String原始碼解讀以及的intern()方法探究

String原始碼解讀以及的intern()方法探究

String的屬性

先看下String的屬性如下:

/**通過陣列儲存值*/
private final char value[];
/** 預設hashcode 為0 */
private int hash; // Default to 0
/**描述序列化類中的序列化欄位*/
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];

hashCode()計算方式如下:

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計算,傳說是能更多的避免hash值重複與編譯器優化,沒怎麼明白

建構函式

給出幾個常見的知道編碼方式的建構函式

/**用位元組陣列構建字串,並指定編碼名稱*/
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);
    }

/**用位元組陣列構建字串,並指定編碼集*/
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[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
    }

intern()方法

返回: 一個字串,內容與此字串相同,但它保證來自字串池中。

public String intern()返回字串物件的規範化表示形式。一個初始時為空的字串池,它由類 String 私有地維護。 當呼叫 intern 方法時,如果池已經包含一個等於此 String 物件的字串(該物件由 equals(Object) 方法確定),則返回池中的字串。否則,將此 String 物件新增到池中,並且返回此 String 物件的引用。它遵循對於任何兩個字串 s 和 t,當且僅當 s.equals(t) 為 true 時,s.intern() == t.intern()才為 true。 所有字面值字串和字串賦值常量表達式都是內部的。

我們知道,一個Java程式執行後,String類會在記憶體的方法區中維護一個字串池。對一個字串呼叫intern()方法後,會先檢查池內是否有該字串,若有則返回;若沒有沒有則先建立再返回,確保返回的字串已經以字面量的形式存在於池中。

public class Test {
	public static void main(String argv[])
	{
		String s1 = "HelloWorld";
		String s2 = new String("HelloWorld");
		String s3 = "Hello";
		String s4 = "World";
		String s5 = "Hello" + "World";
		String s6 = s3 + s4;
		
		System.out.println(s1 == s2);
		System.out.println(s1 == s5);
		System.out.println(s1 == s6);
		System.out.println(s1 == s6.intern());
		System.out.println(s2 == s2.intern());
	}
}

執行結果如下:

false

true

false

true

false


解釋一下:  s1 建立的 HelloWorld 存在於方法區中的常量池其中的字串池,而 s2 建立的 HelloWorld 存在於堆中,故第一條 false 。  s5 的建立是先進行右邊表示式的字串連線,然後才對 s5 進行賦值。賦值的時候會搜尋池中是否有 HelloWorld 字串,若有則把 s5 引用指向該字串,若沒有則在池中新增該字串。顯然 s5 的建立是用了 s1 已經建立好的字面量,故 true 。  第三個比較容易弄錯,s6 = s3 + s4;中間多出來了一個StringBuilder物件, 其實相當於 s6 = new String(s3 + s4); s6 是存放於堆中的,不是字面量。所以 s1 不等於 s6 。  第四個 s6.intern() 首先獲取了 s6 的 HelloWorld 字串,然後在字串池中查詢該字串,找到了 s1 的 HelloWorld 並返回。這裡如果事前字串池中沒有 HelloWorld 字串,那麼還是會在字串池中建立一個 HelloWorld 字串再返回。總之返回的不是堆中的 s6 那個字串。  第四條也能解釋為什麼第五條是 false 。s2是堆中的 HelloWorld,s2.intern() 是字串池中的 HelloWorld 。