Java面試題之包裝類快取機制

Bug如風常伴吾身發表於2018-01-26

Java包裝類快取機制

引言

在我人生中第一次找工作時(大四實習),曾經出現了這樣一道筆試題,求其結果。

Integer a = 100;
Integer b = 100;
Integer c = new Integer(100);
Integer d = new Integer(100);
Integer e = 1000;
Integer f = 1000;
System.out.println(a==b);
System.out.println(a==c);
System.out.println(c==d);
System.out.println(e==f);

我記得我當時答案是:false,false,false,false。哈哈,全false,當時腦子想的是好像有個快取,valueOf相關,這兒沒有用,都是物件,全錯。
然後…………光榮OUT。
正確答案是:true,false,false,false

一言不合,先反編譯

Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
Integer c = new Integer(100);
Integer d = new Integer(100);
Integer e = Integer.valueOf(1000);
Integer f = Integer.valueOf(1000);
System.out.println(a == b);
System.out.println(a == c);
System.out.println(c == d);
System.out.println(e == f);

對,你沒有看錯,Integer a = 100 ,變成了Integer a = Integer.valueOf(100)
這涉及到了Java中的裝箱轉換。
在《Java語言規範》第三版中5.1.7節有一下一段話。

裝箱轉換將把基本型別的值轉換為相應的引用型別的值
……
……
如果被裝箱的值是true、false, —個byte、一個在\u0000~\u007f之間的char。
或者一個在-128~127之間的int或short數字,設r1和是r2的任何兩個裝箱轉換的結果,則始終有r1=r2

話是這樣說,怎麼實現呢,我們通過Integer程式碼說明(我使用的是JDK9)

Integer快取機制實現

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

上面這段話的意思就是,如果傳入的值在[IntegerCache.low,IntegerCache.high]之間,則返回IntegerCache中的cache陣列中對應的值。(比如 i = 100,low = -128,high = 127,則返回cache[228])
那這個IntegerCache又是什麼東西呢,它是Integer中的一個私有靜態內部類

private static class IntegerCache {
    static final int low = -128;//##cache的最低值為-128
    static final int high;//##最大值未定義,此時預設為0
    static final Integer cache[];//##定義一個Integer陣列,存放Integers

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);//##獲取預設值和設定的值中的最大值
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);//##最小值-127不變,h就是i和Integer.MAX_VALUE-128中的最小值。總不能超過int能表示的數
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];//初始化陣列
        int j = low;
        for(int k = 0; k < cache.length; k++)//##遍歷賦值從-128,-127...-->cache.length
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        //##根據Java語言規範   -128, 127之間必須被快取  當最大值小於127時程式將會異常終止java.lang.AssertError
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

上述程式碼中//##是我所備註註釋。

設定IntegerCache.high

-Djava.lang.Integer.IntegerCache.high=xxx

xxx為你想要設定的數字大小 [-128,xxx]範圍之內將會被快取
比如:下圖所示 [-128,300]範圍之內將會被快取
這裡寫圖片描述

包裝類快取機制原因

一個工程中,像這種數字物件,時不時就要建立一個,如果有大量物件時,有可能會造成記憶體溢位,我們把其中共同的部分抽象出來,如果有相同的業務請求,直接返回在記憶體中已有的物件,避免重新建立。
於是,我們的Java採用了享元模式來設計這些包裝類(String也是)。

享元模式(Flyweight Pattern)
主要用於減少建立物件的數量,以減少記憶體佔用和提高效能。這種型別的設計模式屬於結構型模式,它提供了減少物件數量從而改善應用所需的物件結構的方式。

引用自菜鳥教程,它對其進行了詳細的介紹。

其他包裝類快取機制原始碼

Boolean快取機制實現

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

Byte快取機制實現

private static class ByteCache {
    private ByteCache(){}

    static final Byte cache[] = new Byte[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Byte((byte)(i - 128));
    }
}
public static Byte valueOf(byte b) {
    final int offset = 128;
    return ByteCache.cache[(int)b + offset];
}

Short快取機制實現

private static class ShortCache {
    private ShortCache(){}

    static final Short cache[] = new Short[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Short((short)(i - 128));
    }
}
public static Short valueOf(short s) {
    final int offset = 128;
    int sAsInt = s;//##先轉為int
    if (sAsInt >= -128 && sAsInt <= 127) { // must cache
        return ShortCache.cache[sAsInt + offset];
    }
    return new Short(s);
}

Long快取機制實現

private static class LongCache {
    private LongCache(){}

    static final Long cache[] = new Long[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Long(i - 128);
    }
}
public static Long valueOf(long l) {
    final int offset = 128;
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

Float和Double未使用快取

public static Float valueOf(float f) {
  return new Float(f);
}
public static Double valueOf(double d) {
    return new Double(d);
}

既然說了自動裝箱,就可以提一下自動拆箱

#自動拆箱

《Java語言規範》第三版中5.1.8節

拆箱轉換把引用型別的值轉換成相應的基本型別的值。

Integer a = 100;
int b = a;

反編譯之後。

Integer a = Integer.valueOf(100);;
int b = a.intValue();

其他包裝類一樣,都是通過intValue()獲取被包裝的值。

#總結

可以看的出,這些包裝類中(除了浮點數和雙精度數)都使用了快取機制,他確確實實減少建立物件的數量,減少了記憶體的佔用和提高了效能。但是開發者有時不會注意到這些細節,可能會引起一些程式邏輯上的問題,所以一定要注意這些使用了快取的包裝類之間的對比。

相關文章