糟糕,三妹連緩存池都不知道!
“三妹,今天我們來補一個小的知識點:Java 數(shù)據(jù)類型緩存池?!蔽液攘艘豢阼坭脚莸牟韬髮θ谜f,“考你一個問題哈:new Integer(18) 與 Integer.valueOf(18) 的區(qū)別是什么?”
“難道不一樣嗎?”三妹有點詫異。
“不一樣的?!蔽倚χf。
new Integer(18)每次都會新建一個對象;Integer.valueOf(18)會使?用緩存池中的對象,多次調(diào)用只會取同?一個對象的引用。
來看下面這段代碼:
Integer x = new Integer(18);
Integer y = new Integer(18);
System.out.println(x == y);
Integer z = Integer.valueOf(18);
Integer k = Integer.valueOf(18);
System.out.println(z == k);
Integer m = Integer.valueOf(300);
Integer p = Integer.valueOf(300);
System.out.println(m == p);
來看一下輸出結(jié)果吧:
false
true
false
“第一個 false,我知道原因,因為 new 出來的是不同的對象,地址不同?!比媒忉尩溃暗诙€和第三個我認為都應該是 true 啊,為什么第三個會輸出 false 呢?這個我理解不了。”
“其實原因也很簡單?!蔽倚赜谐芍竦卣f。
基本數(shù)據(jù)類型的包裝類除了 Float 和 Double 之外,其他六個包裝器類(Byte、Short、Integer、Long、Character、Boolean)都有常量緩存池。
Byte:-128~127,也就是所有的 byte 值 Short:-128~127 Long:-128~127 Character:\u0000 - \u007F Boolean:true 和 false
拿 Integer 來舉例子,Integer 類內(nèi)部中內(nèi)置了 256 個 Integer 類型的緩存數(shù)據(jù),當使用的數(shù)據(jù)范圍在 -128~127 之間時,會直接返回常量池中數(shù)據(jù)的引用,而不是創(chuàng)建對象,超過這個范圍時會創(chuàng)建新的對象。
18 在 -128~127 之間,300 不在。
來看一下 valueOf 方法的源碼吧。
public static Integer valueOf(int i) {
if (i >=IntegerCache.low && i <=IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
“哦,原來是因為 Integer.IntegerCache 這個內(nèi)部類的原因??!”三妹好像發(fā)現(xiàn)了新大陸。
“是滴。來看一下 IntegerCache 這個靜態(tài)內(nèi)部類的源碼吧?!?/p>
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.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);
} 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++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert Integer.IntegerCache.high >= 127;
}
private IntegerCache() {}
}
之前我們在學習 static 關鍵字的時候,提到過靜態(tài)代碼塊,還記得吧?三妹。靜態(tài)代碼塊通常用來初始化一些靜態(tài)變量,它會優(yōu)先于 main() 方法執(zhí)行。
在靜態(tài)代碼塊中,low 為 -128,也就是緩存池的最小值;high 默認為 127,也就是緩存池的最大值,共計 256 個。
可以在 JVM 啟動的時候,通過 -XX:AutoBoxCacheMax=NNN 來設置緩存池的大小,當然了,不能無限大,最大到 Integer.MAX_VALUE -129
之后,初始化 cache 數(shù)組的大小,然后遍歷填充,下標從 0 開始。
“明白了吧?三妹?!蔽液攘艘豢谒?,扭頭看了看旁邊的三妹。
“這段代碼不難理解,難理解的是 assert Integer.IntegerCache.high >= 127;,這行代碼是干嘛的呀?”三妹很是不解。
“哦哦,你挺細心的呀!”三妹真不錯,求知欲望越來越強烈了。
assert 是 Java 中的一個關鍵字,寓意是斷言,為了方便調(diào)試程序,并不是發(fā)布程序的組成部分。
默認情況下,斷言是關閉的,可以在命令行運行 Java 程序的時候加上 -ea 參數(shù)打開斷言。
來看這段代碼。
public class AssertTest {
public static void main(String[] args) {
int high = 126;
assert high >= 127;
}
}
假設手動設置的緩存池大小為 126,顯然不太符合緩存池的預期值 127,結(jié)果會輸出什么呢?
直接在 Intellij IDEA 中打開命令行終端,進入 classes 文件,執(zhí)行:
/usr/libexec/java_home -v 1.8 --exec java -ea com.itwanger.s51.AssertTest
我用的 macOS 環(huán)境,裝了好多個版本的 JDK,該命令可以切換到 JDK 8
也可以不指定 Java 版本直接執(zhí)行(加上 -ea 參數(shù)):
java -ea com.itwanger.s51.AssertTest
“呀,報錯了呀?!比煤暗?。
Exception in thread "main" java.lang.AssertionError
at com.itwanger.s51.AssertTest.main(AssertTest.java:9)
“是滴,因為 126 小于 127?!蔽一卮鸬馈?/p>
“原來 asset 是這樣用的啊,我明白了。”三妹表示學會了。
“那,緩存池之所以存在的原因也是因為這樣做可以提高程序的整體性能,因為相對來說,比如說 Integer,-128~127 這個范圍內(nèi)的 256 個數(shù)字使用的頻率會高一點?!蔽铱偨Y(jié)道。
“get 了!二哥你真棒,又學到了?!比煤荛_心~
PS:點擊「閱讀原文」可直達《教妹學Java》專欄的碼云在線地址,可以 star 安排一下了!
https://gitee.com/itwanger/jmx-java
三妹:給二哥點個贊吧,讓《教妹學Java》這么通俗易懂、風趣幽默的 Java 教程被更多初學者看得到~
