在Java程式設計中,Integer
類作為基本型別int的包裝器,提供了物件化的操作和自動裝箱與拆箱的功能。從JDK5
開始引入了一項特別的最佳化措施——Integer快取機制,它對於提升程式效能和減少記憶體消耗具有重要意義。接下來我們由一段程式碼去開啟Integer快取機制的秘密。
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
System.out.println(i1 == i2);
Integer i3 = 1000;
Integer i4 = 1000;
System.out.println(i3 == i4);
}
至於答案是什麼呢?我們接著往下看,等你看完就明白了。
當你在你的Idea中寫出這段程式碼的時候,Idea就會提示你要使用
equals()
方法區比較大小,因為Integer
是物件,物件的值比較要用equals()
方法,而不是使用==
,這裡我們主要是研究一下Integer
的快取機制。
Integer快取是什麼
Java的Integer
類內部實現了一個靜態快取池,用於儲存特定範圍內的整數值對應的Integer
物件。預設情況下,這個範圍是-128至127。當透過Integer.valueOf(int)
方法建立一個在這個範圍內的整數物件時,並不會每次都生成新的物件例項,而是複用快取中的現有物件。我們看一下Integer.valueOf(int)
的原始碼:
@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
對於Integer.valueOf(int)
方法來說,由於這個方法經常用於將基本型別int轉換為包裝器物件,所以它使用了@HotSpotIntrinsicCandidate
註解,這樣HotSpot JVM可能會提供一種更為高效的內部實現來處理自動裝箱操作。而IntegerCache
是Integer
內部的一個靜態類,負責快取整數物件。它在類載入時被初始化,建立並快取範圍內的所有整數物件。我們看一下IntegerCache
的原始碼:
private static class IntegerCache {
// 快取範圍的下限,預設為-128
static final int low = -128;
// 快取範圍的上限,初始化時動態計算(基於系統屬性或預設值127)
static final int high;
// 儲存在快取範圍內所有Integer物件的陣列
static final Integer cache[];
// 靜態初始化塊,在類載入時執行
static {
// 初始設定high為127
int h = 127;
// 嘗試從系統屬性獲取使用者自定義的最大整數值
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
// 如果系統屬性存在並且可以轉換為int型別,則更新high值
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
// 確保high至少為127,並且不超過Integer.MAX_VALUE允許的最大陣列大小
h = Math.max(i, 127);
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
// 設定最終確定的high值
high = h;
// 初始化cache陣列,長度等於快取範圍內的整數數量
cache = new Integer[(high - low) + 1];
// 使用迴圈填充cache陣列,建立並儲存對應的Integer物件
int j = low;
for(int k = 0; k < cache.length; k++) {
cache[k] = new Integer(j++);
}
// 檢查,確保快取範圍至少包含[-128, 127]
// 這是Java語言規範對小整數自動裝箱共享的要求
assert IntegerCache.high >= 127;
}
// 私有構造器,防止外部例項化此內部類的物件
private IntegerCache() {}
}
IntegerCache
類在Java虛擬機器啟動時建立了一個固定大小的陣列,用於快取指定範圍內所有的Integer
物件。這樣在後續程式執行過程中,對於這些範圍內的整數進行裝箱操作時,可以直接從快取中獲取已存在的物件,以提升效能並減少記憶體開銷。同時,它也提供了根據系統屬性(-Djava.lang.Integer.IntegerCache.high
)來自定義快取上限的能力,並確保滿足Java語言規範關於小整數自動裝箱共享的規定。
在Integer.value(int)
方法中,如果int
的值在IntegerCache
返回的low
和high
之內,則直接返回IntegerCache
中快取的物件,否則重新new
一個新的Integer
物件。
而文章開頭示例中,我們使用Interge i1 = 100
的方式其實是Java的自動裝箱機制,整數字面量100
是一個基本型別的int值。當賦值給一個Integer
引用變數i
時,編譯器會隱式地呼叫Integer.valueOf(int)
方法將這個基本型別的int值轉換為Integer
物件。
整數在程式設計中經常被使用,特別是在迴圈計數等場景中,透過快取整數物件,可以大幅度減少相同整數值的物件建立,從而減小記憶體佔用。
由此我們可以看出因為100在[-128, 127]之內,所以i1 == i2
列印true
,而1000不在[-128, 127]之內,所以i3 == i4
列印false
。
我們嘗試使用java.lang.Integer.IntegerCache.high
調整一下high
為1000,然後看一下效果:
列印結果都是true。
當然這個上限不要隨意去調整,調整之前,需要仔細評估應用程式的實際需求和效能影響。儘量選擇在[-128, 127]範圍內的整數值,以充分利用Integer快取機制。
注意事項
-
比較: 由於快取的存在,在-128至127之間的
Integer
物件在進行==
運算子比較時,結果可能是true
,因為它們指向的是同一個記憶體地址。而在快取範圍之外建立的Integer
物件即使值相等,也會視為不同的物件,因此使用==
比較會返回false
。不論是否啟用快取,對於任何兩個Integer
物件,只要其包含的整數值相同,呼叫equals()
方法始終會返回true
。所以我們在比較物件時一定要使用equals()
方法。 -
不適用於所有場景: 當使用
new Integer(i)
直接建立Integer
物件時,不會利用快取。 -
不要隨意去擴充套件快取的上下限
總結
Integer快取機制是Java中的一項效能最佳化措施,透過快取一定範圍內的整數物件,既能減小記憶體開銷,又能提高效能。
本文已收錄於我的個人部落格:碼農Academy的部落格,專注分享Java技術乾貨,包括Java基礎、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中介軟體、架構設計、面試題、程式設計師攻略等