探究Java中Integer緩沖區(qū)底層原理
一. Integer底層原理探究
1. int和Integer的區(qū)別
除了要掌握Integer的用法之外,我們還要了解它的一些底層內(nèi)容,因?yàn)樵诿嬖嚂r(shí),關(guān)于Integer的底層考察的比較多。比如一個(gè)常見的面試題是這樣的:請(qǐng)問(wèn)int和Integer的區(qū)別有哪些?
面對(duì)這樣的一道題目,你該怎么回答?常規(guī)的答案其實(shí)很容易答出來(lái),比如:
- int是基本數(shù)據(jù)類型,代表整型數(shù)據(jù),默認(rèn)值是0;
- Integer是 int的包裝類,屬于引用類型,默認(rèn)值為 null ;
- int 和 Integer 都可以表示某一個(gè)整型數(shù)值;
- Integer變量實(shí)際是對(duì)象的引用,當(dāng)new一個(gè)Integer時(shí),實(shí)際上是生成一個(gè)指針指向此對(duì)象;而int則是直接存儲(chǔ)數(shù)據(jù)值;
- Integer可以區(qū)分出未賦值和值為 0 的區(qū)別,而int 則無(wú)法表達(dá)出未賦值的情況;
- int 和 Integer 不能夠互用,因?yàn)樗麄兪莾煞N不同的數(shù)據(jù)類型;
- int在初始化時(shí),可以直接寫成 int=1 的形式;
- 因?yàn)镮nteger是包裝類型,使用時(shí)可以采用
Integer i = new Integer(1)
的形式,但因?yàn)镴ava中的自動(dòng)裝箱和拆箱機(jī)制,使得對(duì)Integer類的賦值也可以使用Integer i= 1
的形式;- 如果我們只是進(jìn)行一些加減乘除的運(yùn)算 或者 作為參數(shù)進(jìn)行傳遞,那么就可以直接使用int這樣的基本數(shù)據(jù)類型;但如果想按照對(duì)象來(lái)進(jìn)行操作處理,那么就要使用Integer來(lái)聲明一個(gè)對(duì)象。
但是如果你只能回答出這樣的答案,你在面試官的眼里只能算合格,還算不上優(yōu)秀,我們需要對(duì)Integer了解地更多一些。
2. 被final修飾的Integer類
為了搞清楚Integer的底層,我們就不得不研究一下它的源碼,我們來(lái)追蹤一下Integer源碼,如下圖所示:
從源碼中可以看出,Integer是Number的一個(gè)子類,且被final所修飾!請(qǐng)大家回顧一些之前講過(guò)的final知識(shí)點(diǎn)。
我們知道,被final修飾的類是常量類,該類不能被繼承,里面的方法不能被重寫,創(chuàng)建出的對(duì)象也不能被修改!總之,Integer符合final類的特征。
3. IntegerCache緩沖區(qū)
我們還記得,在Integer中有一個(gè)valueOf()方法,該方法可以將int值轉(zhuǎn)為Integer對(duì)象。接下來(lái)我們來(lái)看看該方法的實(shí)現(xiàn)源碼,如下圖所示:
從上圖的源碼截圖中我們可以看到,Integer中有一個(gè)緩沖區(qū)叫做IntegerCache,這是Integer中的一個(gè)內(nèi)部類,如下圖所示:
我們可以看到,low就是-128,high等于127,這是緩沖區(qū)的最低和最高邊界。那么這個(gè)緩沖區(qū)的存在到底有什么用呢?大家別著急,我們先做幾個(gè)核心實(shí)驗(yàn)。
4. 幾個(gè)核心實(shí)驗(yàn)
為了能夠講清楚Integer的底層邏輯,小編給大家設(shè)計(jì)了如下代碼,用于驗(yàn)證Integer的底層設(shè)計(jì)。
4.1 比較new出的兩個(gè)Integer對(duì)象
我們通過(guò)new對(duì)象的方式,來(lái)創(chuàng)建兩個(gè)Integer對(duì)象i和j,并比較這兩個(gè)對(duì)象。
//通過(guò)new生成的兩個(gè)Integer變量進(jìn)行比較,結(jié)果為false Integer i = new Integer(100); Integer j = new Integer(100); System.out.print(i == j); //false
從運(yùn)行的結(jié)果中可以看出,通過(guò)new生成的兩個(gè)Integer對(duì)象永遠(yuǎn)是不會(huì)相等的。這是因?yàn)閚ew生成的是兩個(gè)對(duì)象,Integer變量實(shí)際上是對(duì)Integer對(duì)象的引用,這兩個(gè)對(duì)象的內(nèi)存地址是不同的。
4.2 Integer對(duì)象和int變量進(jìn)行比較
接下來(lái)我們?cè)诎岩粋€(gè)Integer對(duì)象和int變量進(jìn)行比較,如下:
Integer i = new Integer(100); int j = 100; System.out.print(i == j); //true
Integer變量和int變量進(jìn)行比較時(shí),只要兩個(gè)變量的值是相等的,結(jié)果就為true。這是因?yàn)镮nteger包裝類和int基本類型進(jìn)行比較時(shí),Java會(huì)進(jìn)行自動(dòng)拆箱操作,將Integer轉(zhuǎn)為了int,然后再進(jìn)行比較,實(shí)際上就變?yōu)榱藘蓚€(gè)int變量的比較。本案例中兩者的值都是100,所以用“==”等號(hào)進(jìn)行比較時(shí)自然就是相等的。
4.3 非new的Integer變量和new出的Integer變量進(jìn)行比較
然后我們?cè)侔岩粋€(gè)非new的Integer變量和new出的Integer變量進(jìn)行比較,如下所示:
//非new生成的Integer變量和new Integer()生成的變量進(jìn)行比較 Integer i = new Integer(100); //自動(dòng)裝箱 Integer j = 100; System.out.print(i == j); //false
在這段代碼中,非new生成的Integer變量和new Integer()生成的變量進(jìn)行比較時(shí),結(jié)果卻為false ! 這是因?yàn)榉莕ew生成Integer變量時(shí),內(nèi)部會(huì)調(diào)用valueOf()方法,進(jìn)行自動(dòng)裝箱操作,此時(shí)會(huì)把Integer變量的值指向Java常量池中的數(shù)據(jù)。而new Integer()生成的變量,則指向的是堆中新建的對(duì)象,兩者在內(nèi)存中的地址是不同的。
4.4 兩個(gè)非new生成的Integer對(duì)象進(jìn)行比較
接著我們?cè)賹?duì)兩個(gè)非new生成的Integer對(duì)象進(jìn)行比較,如下所示:
//兩個(gè)非new生成的Integer對(duì)象進(jìn)行比較 //i與j的取值范圍是在 -128~127 之間! Integer i = 100; Integer j = 100; System.out.print(i == j); //true //x與y的取值范圍不在 -128~127 之間! Integer x = 200; Integer y = 200; System.out.print(x == y); //false
這段代碼中,兩個(gè)非new生成的Integer對(duì)象進(jìn)行比較時(shí),如果兩個(gè)變量的取值在 -128到127 之間,則比較結(jié)果為true;如果兩個(gè)變量的值不在此區(qū)間,則比較結(jié)果為false。 這又是為什么呢? 其實(shí)要想弄明白這個(gè)原因,我們只需要看看Integer類的valueOf()方法是怎么寫的就可以了。valueOf()方法源碼如下:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
我們知道,valueOf(int i)方法可以將int值自動(dòng)裝箱變成對(duì)應(yīng)的Integer實(shí)例。 并且從這段源碼中我們可以看到其內(nèi)部有一個(gè)if判斷,根據(jù)判斷結(jié)果的不同,會(huì)有2種不同的方式得到Integer對(duì)象:當(dāng)arg大于等于-128且小于等于127時(shí),則直接從緩存中返回一個(gè)已經(jīng)存在的對(duì)象;如果參數(shù)的值不在這個(gè)范圍內(nèi),則new一個(gè)Integer對(duì)象返回, 即要么new Integer,要么從int常量池中獲取 !
之前我們構(gòu)建Integer對(duì)象的傳統(tǒng)方式是直接 new 一個(gè)Integer對(duì)象,內(nèi)部會(huì)調(diào)用構(gòu)造器。但是根據(jù)實(shí)踐,我們發(fā)現(xiàn)大部分的數(shù)據(jù)操作都是集中在有限的、較小的數(shù)值范圍內(nèi)。因而在JDK 1.5中,新增了一個(gè)靜態(tài)工廠方法valueOf(int i)。當(dāng)我們進(jìn)行Integer i=xxx 賦值操作時(shí),Java內(nèi)部會(huì)調(diào)用執(zhí)行這個(gè)valueOf()實(shí)現(xiàn)自動(dòng)裝箱。而在調(diào)用valueOf()方法時(shí),其內(nèi)部會(huì)利用緩存機(jī)制,對(duì)取值在-128~127之間的int值進(jìn)行緩存操作,這是在JDK 1.5 之后進(jìn)行的一個(gè)可以明顯改善性能的提升 。 而按照J(rèn)avadoc文檔,該緩存機(jī)制默認(rèn)會(huì)緩存在 -128 到 127 之間的值,不在該區(qū)間的值并不會(huì)進(jìn)行緩存。所以,給Integer i
賦值的大小不同,比較的結(jié)果也可能會(huì)不同。
4.5 ==和equals的區(qū)別
最后我們?cè)僮鲆粋€(gè)實(shí)驗(yàn),來(lái)看看==與equals比較兩個(gè)Integer對(duì)象時(shí)有什么不同。
Integer x = 127; Integer y = 127; Integer m = 100000; Integer n = 100000; System.out.println("x == y: " + (x==y)); // true System.out.println("m == n: " + (m==n)); // false System.out.println("x.equals(y): " + x.equals(y)); // true System.out.println("m.equals(n): " + m.equals(n)); // true
從該實(shí)驗(yàn)中可以看出,==比較時(shí),較小的兩個(gè)相同的Integer會(huì)返回true,較大的兩個(gè)相同的Integer會(huì)返回false。結(jié)合上面給大家的講解,你思考一下這是為什么?
5. 結(jié)論
通過(guò)以上的幾個(gè)核心實(shí)驗(yàn),可以給大家梳理出一個(gè)結(jié)論:
當(dāng)我們利用”==“等號(hào)比較兩個(gè)Integer i 和 Integer j
的值時(shí),如果取值范圍是在-128~127之間,兩個(gè)相同的Integer值會(huì)返回true;如果不在該區(qū)間,兩個(gè)相同的Integer值會(huì)返回false。這是因?yàn)镮nteger是final類,編譯器把Integer i = 100; 自動(dòng)變?yōu)镮nteger i = Integer.valueOf(100);
。為了節(jié)省內(nèi)存, Integer.valueOf()
對(duì)于較小的數(shù),始終會(huì)返回相同的實(shí)例對(duì)象,因此,==比較的結(jié)果就是true。
那么如果我們只是為了比較兩個(gè)Integer對(duì)象的值是否相等,而不是為了比較兩個(gè)對(duì)象的地址是否相同,在開發(fā)時(shí)請(qǐng)盡量使用equals()方法,而不是==!
并且我們現(xiàn)在還知道,在Java中有3種方式可以構(gòu)造出一個(gè)Integer對(duì)象,代碼如下:
//方法1: Integer i = new Integer(100); //方法2: Integer i = Integer.valueOf(100); //方法3: Integer i = 100;
實(shí)際上,方法2和方法3的本質(zhì)是一樣的,所以開發(fā)時(shí)為了簡(jiǎn)潔,我們一般是通過(guò)方法3來(lái)得到一個(gè)Integer對(duì)象。但是盡量不要使用方法1來(lái)構(gòu)建Integer對(duì)象,這是因?yàn)榉椒?總是會(huì)創(chuàng)建一個(gè)新的 Integer 實(shí)例,而方法2和方法3則會(huì)盡可能地返回緩存的實(shí)例對(duì)象,以節(jié)省內(nèi)存。
所以最終關(guān)于”int和Integer的區(qū)別有哪些“這道面試題的答案,如果你想拿到高分,就需要把Integer的底層原理也回答出來(lái)才行!如果你可以把以上內(nèi)容都回答清楚,我相信單憑這一道題目,就足以讓面試官對(duì)你刮目相看!
二. 結(jié)語(yǔ)
最后我再梳理一下該問(wèn)題的回答要點(diǎn):
先簡(jiǎn)單回顧Java中的數(shù)據(jù)類型及取值范圍;
然后簡(jiǎn)介基本類型與包裝類,最后還能說(shuō)明為什么需要有包裝類;
接著說(shuō)一下int與Integer的基本區(qū)別;
最后再說(shuō)int與Integer的深入?yún)^(qū)別,即底層的源碼和原理。
如果你可以把我總結(jié)的這4點(diǎn)都能回答好,就這一個(gè)問(wèn)題,面試官就會(huì)對(duì)你留下深刻的影響,他就會(huì)認(rèn)為你的基礎(chǔ)知識(shí)足夠扎實(shí),因?yàn)榇蠖鄶?shù)人只會(huì)回答int和Integer的基本區(qū)別,很少有人去回答底層的內(nèi)容!而通過(guò)這個(gè)問(wèn)題,面試官也會(huì)了解到,你對(duì)Java的內(nèi)存分配是很熟悉的!
以上就是探究Java中Integer緩沖區(qū)底層原理的詳細(xì)內(nèi)容,更多關(guān)于Java Integer底層原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java虛擬機(jī)JVM類加載機(jī)制(從類文件到虛擬機(jī))
所謂的類加載機(jī)制就是虛擬機(jī)將class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證,轉(zhuǎn)換解析和初始化,形成虛擬機(jī)可以直接使用的java類型,本文給大家介紹類加載機(jī)制過(guò)程從類文件到虛擬機(jī)的詳細(xì)說(shuō)明,感興趣的朋友跟隨小編一起看看吧2021-06-06Java Json字符串的雙引號(hào)("")括號(hào)如何去掉
這篇文章主要介紹了Java Json字符串的雙引號(hào)("")括號(hào)如何去掉?具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09Linux中Java開發(fā)常用軟件安裝方法總結(jié)
這篇文章主要介紹了Linux中Java開發(fā)常用軟件安裝方法總結(jié),需要的朋友可以參考下2020-02-02java調(diào)用process線程阻塞問(wèn)題的解決
這篇文章主要介紹了java調(diào)用process線程阻塞問(wèn)題的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06java虛擬機(jī)學(xué)習(xí)筆記進(jìn)階篇
在本篇內(nèi)容里小編給大家分享了關(guān)于java虛擬機(jī)學(xué)習(xí)筆記的進(jìn)階內(nèi)容,需要的朋友們跟著學(xué)習(xí)下。2019-06-06寧可用Lombok也不把成員設(shè)置為public原理解析
這篇文章主要為大家介紹了寧可用Lombok也不把成員設(shè)置為public原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03