Java中String字符串常量池和intern方法源碼分析
一. 常量池簡(jiǎn)介
1. 基本概念
常量池是堆中的一塊存儲(chǔ)區(qū)域,用于存儲(chǔ)顯式的String、float、Integer等數(shù)據(jù)。這是一個(gè)特殊的共享區(qū)域,開(kāi)發(fā)時(shí)不需要在內(nèi)存中經(jīng)常改變的數(shù)據(jù),都可以放在這里進(jìn)行共享。JDK 7及其之前的常量池是在方法區(qū)中,從Java8之后,常量池存放到了堆中。
為了讓大家更好地理解常量池的作用,給大家分析一下String字符串的內(nèi)存分配。
2. 實(shí)驗(yàn)案例
我們先來(lái)編寫一行代碼,如下所示:
//String對(duì)象創(chuàng)建 String s = new String("xyz");
這個(gè)代碼很簡(jiǎn)單,就一行代碼!那么問(wèn)題來(lái)了,這行代碼中幾個(gè)對(duì)象的內(nèi)存分配是如何的?接下來(lái)就給大家把這段代碼的內(nèi)存分區(qū)繪制一下(本案例開(kāi)發(fā)環(huán)境是基于JDK8)。
3. 內(nèi)存分配(重點(diǎn))
在 String s = new String("xyz");
這行代碼中,s是String類型的變量,不是對(duì)象!‘xyz’是字符串對(duì)象,new String("xyz")也是一個(gè)對(duì)象,那么它們幾個(gè)的內(nèi)存劃分在JDK8的環(huán)境中,如下圖所示:
根據(jù)上圖,給大家分析一下上述代碼的內(nèi)存分配情況,如下所示:
當(dāng)JVM在編譯階段加載讀取到“xyz”的時(shí)候,首先會(huì)檢查堆中的String常量池,也就是常量緩沖區(qū)。檢查是否已經(jīng)有了"xyz"常量對(duì)象,如果有,則不會(huì)再次創(chuàng)建"xyz"常量對(duì)象,并直接返回該字符串的引用地址;如果沒(méi)有,則創(chuàng)建一個(gè)"xyz"常量對(duì)象,并為該對(duì)象分配一個(gè)內(nèi)存地址002返回;
當(dāng)JVM在運(yùn)行階段加載讀取到new關(guān)鍵字的時(shí)候,JVM會(huì)在堆中為其創(chuàng)建一個(gè)對(duì)象,即new String(),并為其分配內(nèi)存地址001,而堆中這個(gè)對(duì)象的內(nèi)容是上面"xyz"常量對(duì)象的引用地址002,換句話說(shuō)這個(gè)堆中存的就是常量池中"xyz"的引用地址002;
最后,s 是對(duì)當(dāng)前堆中001號(hào)對(duì)象的一個(gè)地址引用,s本身不是一個(gè)對(duì)象,s只是一個(gè)String類型的變量而已!
二. intern()方法(重點(diǎn))
了解了常量池的內(nèi)容之后,接下來(lái)請(qǐng)大家再跟著小編來(lái)看看String的intern()方法,這個(gè)方法很重要,請(qǐng)大家記住哦。
/** * Returns a canonical representation for the string object. * <p> * A pool of strings, initially empty, is maintained privately by the * class {@code String}. * <p> * When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned. * <p> * It follows that for any two strings {@code s} and {@code t}, * {@code s.intern() == t.intern()} is {@code true} * if and only if {@code s.equals(t)} is {@code true}. * <p> * All literal strings and string-valued constant expressions are * interned. String literals are defined in section 3.10.5 of the * <cite>The Java™ Language Specification</cite>. * * @return a string that has the same contents as this string, but is * guaranteed to be from a pool of unique strings. */ public native String intern();
從上面的源碼注釋中我們可以知道,intern()是由C語(yǔ)言實(shí)現(xiàn)的native底層方法,用于從String緩存池中獲取一個(gè)與該字符串內(nèi)容相同的字符串對(duì)象。當(dāng)這個(gè)intern()方法被執(zhí)行的時(shí)候,如果緩存池中已經(jīng)有這個(gè)String內(nèi)容,則直接從這個(gè)緩存池中獲取該String內(nèi)容對(duì)象;如果緩存池中沒(méi)有這個(gè)String內(nèi)容對(duì)象,則把這個(gè)String內(nèi)容對(duì)象放到緩存池中,并返回這個(gè)字符串對(duì)象的引用。 現(xiàn)在我們知道了intern()方法的功能,但是該方法的底層原理是什么樣的呢?接下來(lái)給結(jié)合一段代碼案例,給各位詳細(xì)說(shuō)一下:
//常量池與堆的關(guān)系 String str1="yiyige"; String str2=new String("yiyige"); System.out.println("str1==str2的結(jié)果==> " +(str1==str2)); String str3 = str2.intern(); System.out.println("str1==str3的結(jié)果==> " +(str1==str3));
執(zhí)行結(jié)果如下圖所示:
intern()方法的底層原理如下(重點(diǎn)):
Java專門為String類設(shè)計(jì)了一個(gè)緩存池intern pool,intern pool是在方法區(qū)中的一塊特殊存儲(chǔ)區(qū)域,當(dāng)我們通過(guò) String str="yiyige" 這樣的方式來(lái)構(gòu)造一個(gè)新的字符串時(shí),String類會(huì)優(yōu)先在緩存池中查找是否已經(jīng)存在內(nèi)容相同的String對(duì)象。如果有則直接返回該對(duì)象的地址引用,如果沒(méi)有就會(huì)構(gòu)造一個(gè)新的String對(duì)象,然后放進(jìn)緩存池,再返回該字符串的地址引用。因此,即使我們構(gòu)造一萬(wàn)個(gè)String str = "yiyige",但實(shí)際上得到的都是同一個(gè)地址引用,這樣就避免了很多不必要的空間開(kāi)銷。注意:intern池不適用new String("yiyige")的構(gòu)造形式!
注意:
因?yàn)樽址A砍卮娣盼恢冒l(fā)生了變化,String類對(duì)intern()方法也進(jìn)行了一些修改:
JDK 6 版本中執(zhí)行intern()方法時(shí),首先會(huì)判斷字符串常量池中是否存在該字符串字面量,如果不存在則拷貝一份字符串字面量存放到常量池中,最后返回該字符串字面量的唯一引用。如果發(fā)現(xiàn)字符串常量池中已經(jīng)存在,則直接返回該字符串字面量的唯一引用。
JDK 7 以后執(zhí)行intern()方法時(shí),如果發(fā)現(xiàn)字符串常量池中不存在該字符串字面量,則不會(huì)再拷貝一份字面量,而是拷貝字面量對(duì)應(yīng)堆中的一個(gè)地址引用,然后返回這個(gè)引用。
現(xiàn)在我們知道了,原來(lái)當(dāng)一個(gè)String對(duì)象被創(chuàng)建時(shí),如果發(fā)現(xiàn)當(dāng)前String對(duì)象已經(jīng)存在于String Pool中了,就會(huì)返回一個(gè)已存在的String對(duì)象引用而不會(huì)新建一個(gè)對(duì)象。比如以下代碼只會(huì)在常量池中創(chuàng)建一個(gè)String對(duì)象。
String str1 = "yiyige"; String str2 = "yiyige";
創(chuàng)建過(guò)程如下圖所示:
如果一個(gè)String是可變的,當(dāng)改變了A引用指向的String時(shí),可能就會(huì)導(dǎo)致其他的B引用得到錯(cuò)誤的值,所以Sting就被設(shè)計(jì)為不可變的。String底層主要是使用intern緩存池將字符串緩存起來(lái),同時(shí)允許把一個(gè)String字符串的地址賦值給多個(gè)String變量來(lái)引用,這樣就可以保證多個(gè)變量安全地共享同一個(gè)對(duì)象。如果Java中的String對(duì)象可變的話,一個(gè)字符串引用的操作改變了對(duì)象的值,那么其他的變量就會(huì)受到影響。
三. 結(jié)語(yǔ)
至此,就把字符串相關(guān)的一些常規(guī)原理性知識(shí)點(diǎn),給大家講解梳理完畢了。
到此這篇關(guān)于Java中String字符串常量池和intern方法源碼分析的文章就介紹到這了,更多相關(guān)Java String常量池和intern方法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Maven中錯(cuò)誤使用parent.relativePath導(dǎo)致構(gòu)建失敗問(wèn)題
這篇文章主要介紹了Maven中錯(cuò)誤使用parent.relativePath導(dǎo)致構(gòu)建失敗問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08java連接mysql數(shù)據(jù)庫(kù)亂碼的解決方法
這篇文章主要介紹通過(guò)java連接mysql數(shù)據(jù)庫(kù)的時(shí)候,頁(yè)面出現(xiàn)亂碼,這里簡(jiǎn)單分享下解決方法, 需要的朋友可以參考下2013-05-05SpringBoot?實(shí)現(xiàn)流控的操作方法
本文介紹了限流算法的基本概念和常見(jiàn)的限流算法,包括計(jì)數(shù)器算法、漏桶算法和令牌桶算法,還介紹了如何在Spring?Boot中使用Guava庫(kù)和自定義注解以及AOP實(shí)現(xiàn)接口限流功能,感興趣的朋友一起看看吧2024-12-12java9的JShell小工具和編譯器兩種自動(dòng)優(yōu)化方法
這篇文章主要介紹了java9的JShell小工具和編譯器兩種自動(dòng)優(yōu)化方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07java?LeetCode刷題稍有難度的貪心構(gòu)造算法
這篇文章主要為大家介紹了java?LeetCode刷題稍有難度的貪心構(gòu)造題解示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Spring三級(jí)緩存思想解決循環(huán)依賴總結(jié)分析
這篇文章主要為大家介紹了Spring三級(jí)緩存思想解決循環(huán)依賴總結(jié)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08解析Spring Mvc Long類型精度丟失問(wèn)題
在平時(shí)開(kāi)發(fā)過(guò)程中,經(jīng)常會(huì)使用long類型作為id的類型,但是在使用過(guò)程中會(huì)導(dǎo)致long類型數(shù)據(jù)轉(zhuǎn)換為number類型時(shí)的后兩位變?yōu)?,今天小編給大家分享Spring Mvc Long類型精度丟失問(wèn)題,需要的朋友參考下吧2021-06-06springboot集成mybatisplus實(shí)例詳解
這篇文章主要介紹了springboot集成mybatisplus實(shí)例詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09