再學Java 之 Integer 包裝類快取

scutwang發表於2014-05-03

前言:本博文將涉及的Java的自動裝箱和自動拆箱,可以參考 這篇文章 和 官方教程 ,這裡不再贅述。

 

首先,先看一個小程式:

public class Main {
    
    public static void main(String[] args){
        Integer i1 = new Integer(1);
        Integer i2 = new Integer(1);
        System.out.println(i1 == i2);
        
        Integer i3 = 1;
        Integer i4 = 1;
        System.out.println(i3 == i4);
        
        Integer i5 = 200;
        Integer i6 = 200;
        System.out.println(i5 == i6);
    }
}

 

上面的程式會依次輸出false 、true、false。

 第一個輸出語句應該比較好理解,就是建立了不同的物件。但是第二跟第三個輸出語句估計很多人就很難理解了。

要解釋這個問題,需要從快取說起。

 

快取

  快取是軟體設計模式中一個非常有用的模式,快取的實現方式有很多,不同方式可能存在效能上的差別。下面給出一個用陣列實現的例項:

  (1)快取類Cache_test

/*
 * <p>
 * 該物件使用陣列實現了快取,也就是,
 * 每一次使用valueOf()建立新物件時,系統將會確認快取中是否已經存在相應的物件(即data相等)。
 * 假如存在,則直接返回快取已存在的物件;
 * 假如不存在,則建立一個新物件,儲存到快取中,並返回新建立的物件。
 * </p>
 * 
 * @author Harvin.
 * @version 1.0
 */
public class Cache_test {
    //需要儲存的資料
    private final String data;
    
    public Cache_test(String data){
        this.data = data;
    }
    public String get_data(){
        return this.data;
    }
    @Override
    //直接判斷是否是指向同一個物件
    public boolean equals(Object obj){
        if (this == obj) {
            return true;
        }
        return false;
    }
    
    
    //定義快取的大小
    private final static int MAX_SIZE = 10;
    //使用陣列來儲存快取
    private static Cache_test[] cache
    = new Cache_test[MAX_SIZE];
    //定義當前快取儲存的位置
    private static int pos = 0;
    
    /* 判斷是否已經快取了包含該data物件的Cache_test物件,
     * 如果存在,則直接返回;
     * 如果不存在,則直接建立後再將其返回
     */
    public static Cache_test valueOf(String data){
        for (int i = 0; i < MAX_SIZE; i++) {
            if (cache[i] != null
                    && cache[i].get_data().equals(data)) {
                return cache[i];
            }
        }
        if(MAX_SIZE == pos){
            cache[0]    = new Cache_test(data);
            pos            = 1;
        }else{
            cache[pos]    = new Cache_test(data);
        }
        return cache[pos++];
    }
}
Cache_test

 

  (2)測試類Main

public class Main {
    
    public static void main(String[] args){
        Cache_test ct1 = new Cache_test("test1");
        Cache_test ct2 = new Cache_test("test1");
        //由於這裡都是直接建立,所以下面會輸出false;
        System.out.println(ct1 == ct2);
        
        Cache_test ct3 = Cache_test.valueOf("test2");
        Cache_test ct4 = Cache_test.valueOf("test2");
        //由於這裡使用的是valueOf()函式,將會使用到快取。所以下面輸出true.
        System.out.println(ct3 == ct4);
    }
}
Main

  上面的例子中,實現原理為:使用一個陣列來快取該類的物件,陣列的長度為MAX_SIZE。每一次呼叫valueOf來建立物件時,快取池將會先去查詢快取池中是否已經存在該物件,如果存在,則直接返回該物件,所以當輸入兩個相同data時,返回回來的物件是同一個,所以上面 ct3 和 ct4 為同一個物件。當快取陣列不存在該物件時,快取池將根據傳入的引數建立一個新的物件,再將其儲存到快取陣列中。另外,在這裡快取池使用的是“先進先出”的原則。

PS:上面例項中,用於Cache_test的建構函式為共有,所以,允許建立不儲存到快取池中的物件,假如要強制使用快取池,則可以將建構函式宣告為private。

  

  瞭解了快取原理後,我們來看看實際JDK中使用了快取的類。

包裝類 Integer 的快取

類似於我們上面提到的快取原理,Integer類如果使用new建構函式來建立物件,則每次都將返回全新的物件;假如採用了valueOf方法來建立物件,則會快取該建立的物件。讓我們來看看原始碼:

private static class IntegerCache {//內部類,注意它的屬性都是定義為static final  
    static final inthigh; //快取上界  
    static final Integer cache[];//cache快取是一個存放Integer型別的陣列  
  
    static {//靜態語句塊  
        final int low = -128;//快取下界,值不可變  
  
        // high value may beconfigured by property  
        int h = 127;// h值,可以通過設定jdk的AutoBoxCacheMax引數調整(參見(3))  
        if (integerCacheHighPropValue !=null) {  
            // Use Long.decode here to avoid invoking methods that  
            // require Integer's autoboxing cache to be initialized  
            // 通過解碼integerCacheHighPropValue,而得到一個候選的上界值  
            int i = Long.decode(integerCacheHighPropValue).intValue();  
            // 取較大的作為上界,但又不能大於Integer的邊界MAX_VALUE  
            i = Math.max(i, 127);//上界最小為127  
            // Maximum array size is Integer.MAX_VALUE  
            h = Math.min(i, Integer.MAX_VALUE - -low);  
        }  
        high = h; //上界確定,此時high預設一般是127  
        // 建立快取塊,注意快取陣列大小  
        cache =new Integer[(high - low) + 1];  
        int j = low;  
        for(int k = 0; k <cache.length; k++)  
            cache[k] =new Integer(j++);// -128到high值逐一分配到快取陣列  
    }  
  
    private IntegerCache() {}//構造方法,不需要構造什麼
Integer

 

 簡單來說,就是使用了一個內部類IntegerCache 來管理快取cache[]。但使用valueOf()方法時,系統將會判斷是否存在於快取池中。然而,請注意,這裡有所不同的是,Integer類在載入時,就已經預先將一部分物件(即從-128到127)建立好了,也就是說每一次呼叫valueOf方法時,假如傳入的值在-127到128之間,則Integer類直接返回已經建立好的物件,假如傳入的引數值在此區間之外,則Integer類會建立一個全新的物件。

 

再看小程式

現在,讓我們重新回來一開始的小程式。

(1)程式中 i1 和 i2 利用其建構函式進行構造,所以,兩者是兩個不同的物件,因此返回false。

(2)通過使用javap 檢視位元組碼,可知 i3 和 i4 、i5 和 i6 的自動裝箱事實上是呼叫了valueOf方法。i3 和 i4 的值在-128到127之間,所以直接使用快取池的物件,而 i5 和 i6 超出該區間,所以建立的是新物件。

由此便可以得知所以輸出結果了。

 

後記

通過資料查詢和原始碼的檢視,可以知道,除了Integer類外,還有Byte、Short、Long、Character也使用了快取,而Flot、Double沒有使用快取。

 

相關資料

Integer中用靜態內部類所作的快取

Java中的裝箱與拆箱

《Java 自動裝箱和拆箱》

 

 

相關文章