欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

String.intern()作用與常量池關(guān)系示例解析

 更新時(shí)間:2023年08月25日 14:08:19   作者:Java技術(shù)棧  
這篇文章主要為大家介紹了String.intern()作用與常量池關(guān)系示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

 Background

在 JAVA 語言中有8中基本類型和一種比較特殊的類型String。這些類型為了使他們在運(yùn)行過程中速度更快,更節(jié)省內(nèi)存,都提供了一種常量池的概念。常量池就類似一個JAVA系統(tǒng)級別提供的緩存。

8種基本類型的常量池都是系統(tǒng)協(xié)調(diào)的,String類型的常量池比較特殊。

它的主要使用方法有兩種:

  • 直接使用雙引號聲明出來的String對象會直接存儲在常量池中。
  • 如果不是用雙引號聲明的String對象,可以使用String提供的intern方法。intern 方法會從字符串常量池中查詢當(dāng)前字符串是否存在,若不存在就會將當(dāng)前字符串放入常量池中

推薦一個開源免費(fèi)的 Spring Boot 實(shí)戰(zhàn)項(xiàng)目:

https://github.com/javastacks/spring-boot-best-practice

1.常量池是什么?

JVM常量池主要分為Class文件常量池、運(yùn)行時(shí)常量池,全局字符串常量池,以及基本類型包裝類對象常量池

方法區(qū)

方法區(qū)的作用是存儲Java類的結(jié)構(gòu)信息,當(dāng)創(chuàng)建對象后,對象的類型信息存儲在方法區(qū)中,實(shí)例數(shù)據(jù)存放在堆中。類型信息是定義在Java代碼中的常量、靜態(tài)變量、以及類中聲明的各種方法,方法字段等;實(shí)例數(shù)據(jù)則是在Java中創(chuàng)建的對象實(shí)例以及他們的值。

該區(qū)域進(jìn)行內(nèi)存回收的主要目的是對常量池的回收和對內(nèi)存數(shù)據(jù)的卸載;一般說這個區(qū)域的內(nèi)存回收率比起Java堆低得多。

Class文件常量池

class文件是一組以字節(jié)為單位的二進(jìn)制數(shù)據(jù)流,在Java代碼的編譯期間,我們編寫的Java文件就被編譯為.class文件格式的二進(jìn)制數(shù)據(jù)存放在磁盤中,其中就包括class文件常量池。

class文件常量池主要存放兩大常量:字面量和符號引用。

字面量:字面量接近java語言層面的常量概念

  • 文本字符串,也就是我們經(jīng)常申明的:public String s = "abc";中的"abc"
  • 用final修飾的成員變量,包括靜態(tài)變量、實(shí)例變量和局部變量:public final static int f = 0x101;,final int temp = 3;
  • 而對于基本類型數(shù)據(jù)(甚至是方法中的局部變量),如int value = 1常量池中只保留了他的的字段描述符int和字段的名稱value,他們的字面量不會存在于常量池。

符號引用:符號引用主要設(shè)涉及編譯原理方面的概念

  • 類和接口的全限定名,也就是java/lang/String;這樣,將類名中原來的".“替換為”/"得到的,主要用于在運(yùn)行時(shí)解析得到類的直接引用
  • 字段的名稱和描述符,字段也就是類或者接口中聲明的變量,包括類級別變量和實(shí)例級的變量
  • 方法中的名稱和描述符,也即參數(shù)類型+返回值

運(yùn)行時(shí)常量池

當(dāng)Java文件被編譯成class文件之后,會生成上面的class文件常量池,JVM在執(zhí)行某個類的時(shí)候,必須經(jīng)過加載、鏈接(驗(yàn)證、準(zhǔn)備、解析)、初始化的步鄹,運(yùn)行時(shí)常量池則是在JVM將類加載到內(nèi)存后,就會將class常量池中的內(nèi)容存放到運(yùn)行時(shí)常量池中,也就是class常量池被加載到內(nèi)存之后的版本,是方法區(qū)的一部分。

在解析階段,會把符號引用替換為直接引用,解析的過程會去查詢字符串常量池,也就StringTable,以保證運(yùn)行時(shí)常量池所引用的字符串與字符串常量池中是一致的。

運(yùn)行時(shí)常量池相對于class常量池一大特征就是具有動態(tài)性,Java規(guī)范并不要求常量只能在運(yùn)行時(shí)才產(chǎn)生,也就是說運(yùn)行時(shí)常量池的內(nèi)容并不全部來自class常量池,在運(yùn)行時(shí)可以通過代碼生成常量并將其放入運(yùn)行時(shí)常量池中,這種特性被用的最多的就是String.intern()。

字符串常量池

在JDK6.0及之前版本,字符串常量池存放在方法區(qū)中,在JDK7.0版本以后,字符串常量池被移到了堆中了。至于為什么移到堆內(nèi),大概是由于方法區(qū)的內(nèi)存空間太小了。在HotSpot VM里實(shí)現(xiàn)的string pool功能的是一個StringTable類,它是一個Hash表,默認(rèn)值大小長度是1009;這個StringTable在每個HotSpot VM的實(shí)例只有一份,被所有的類共享。字符串常量由一個一個字符組成,放在了StringTable上。

在JDK6.0中,StringTable的長度是固定的,長度就是1009,因此如果放入String Pool中的String非常多,就會造成hash沖突,導(dǎo)致鏈表過長,當(dāng)調(diào)用String#intern()時(shí)會需要到鏈表上一個一個找,從而導(dǎo)致性能大幅度下降;在JDK7.0中,StringTable的長度可以通過參數(shù)指定。

字符串常量池設(shè)計(jì)思想:

  • 字符串的分配,和其他的對象分配一樣,耗費(fèi)高昂的時(shí)間與空間代價(jià),作為最基礎(chǔ)的數(shù)據(jù)類型,大量頻繁的創(chuàng)建字符串,極大程度地影響程序的性能
  • JVM為了提高性能和減少內(nèi)存開銷,在實(shí)例化字符串常量的時(shí)候進(jìn)行了一些優(yōu)化
    • 為字符串開辟一個字符串常量池,類似于緩存區(qū)
    • 創(chuàng)建字符串常量時(shí),首先查看字符串常量池是否存在該字符串
    • 存在該字符串,返回引用實(shí)例,不存在,實(shí)例化該字符串并放入池中
  • 實(shí)現(xiàn)的基礎(chǔ)
    • 實(shí)現(xiàn)該優(yōu)化的基礎(chǔ)是因?yàn)樽址遣豢勺兊?,可以不用?dān)心數(shù)據(jù)沖突進(jìn)行共享
    • 運(yùn)行時(shí)實(shí)例創(chuàng)建的全局字符串常量池中有一個表,總是為池中每個唯一的字符串對象維護(hù)一個引用,這就意味著它們一直引用著字符串常量池中的對象,所以,在常量池中的這些字符串不會被垃圾收集器回收

2. String.intern()與字符串常量池

/**
 * Returns a canonical representation for the string object.
 * <p>
 * A pool of strings, initially empty, is maintained privately by the
 * class <code>String</code>.
 * <p>
 * When the intern method is invoked, if the pool already contains a
 * string equal to this <code>String</code> object as determined by
 * the {@link #equals(Object)} method, then the string from the pool is
 * returned. Otherwise, this <code>String</code> object is added to the
 * pool and a reference to this <code>String</code> object is returned.
 * <p>
 * It follows that for any two strings <code>s</code> and <code>t</code>,
 * <code>s.intern() == t.intern()</code> is <code>true</code>
 * if and only if <code>s.equals(t)</code> is <code>true</code>.
 * <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&trade; 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();

字符串常量池的位置也是隨著jdk版本的不同而位置不同。在jdk6中,常量池的位置在永久代(方法區(qū))中,此時(shí)常量池中存儲的是對象。在jdk7中,常量池的位置在堆中,此時(shí),常量池存儲的就是引用了。

在jdk8中,永久代(方法區(qū))被元空間取代了。這里就引出了一個很常見很經(jīng)典的問題,看下面這段代碼。

@Test
public void test(){
    String s = new String("2");
    s.intern();
    String s2 = "2";
    System.out.println(s == s2);

    String s3 = new String("3") + new String("3");
    s3.intern();
    String s4 = "33";
    System.out.println(s3 == s4);
}

//jdk6
//false
//false

//jdk7
//false
//true

這段代碼在jdk6中輸出是false false,但是在jdk7中輸出的是false true。我們通過圖來一行行解釋。

JDK1.6

  • String s = new String("2");創(chuàng)建了兩個對象,一個在堆中的StringObject對象,一個是在常量池中的“2”對象。
  • s.intern();在常量池中尋找與s變量內(nèi)容相同的對象,發(fā)現(xiàn)已經(jīng)存在內(nèi)容相同對象“2”,返回對象2的地址。
  • String s2 = "2";使用字面量創(chuàng)建,在常量池尋找是否有相同內(nèi)容的對象,發(fā)現(xiàn)有,返回對象"2"的地址。
  • System.out.println(s == s2);從上面可以分析出,s變量和s2變量地址指向的是不同的對象,所以返回false
  • String s3 = new String("3") + new String("3");創(chuàng)建了兩個對象,一個在堆中的StringObject對象,一個是在常量池中的“3”對象。中間還有2個匿名的new String(“3”)我們不去討論它們。
  • s3.intern();在常量池中尋找與s3變量內(nèi)容相同的對象,沒有發(fā)現(xiàn)“33”對象,在常量池中創(chuàng)建“33”對象,返回“33”對象的地址。
  • String s4 = "33";使用字面量創(chuàng)建,在常量池尋找是否有相同內(nèi)容的對象,發(fā)現(xiàn)有,返回對象"33"的地址。
  • System.out.println(s3 == s4);從上面可以分析出,s3變量和s4變量地址指向的是不同的對象,所以返回false

JDK1.7

  • String s = new String("2");創(chuàng)建了兩個對象,一個在堆中的StringObject對象,一個是在堆中的“2”對象,并在常量池中保存“2”對象的引用地址。
  • s.intern();在常量池中尋找與s變量內(nèi)容相同的對象,發(fā)現(xiàn)已經(jīng)存在內(nèi)容相同對象“2”,返回對象“2”的引用地址。
  • String s2 = "2";使用字面量創(chuàng)建,在常量池尋找是否有相同內(nèi)容的對象,發(fā)現(xiàn)有,返回對象“2”的引用地址。
  • System.out.println(s == s2);從上面可以分析出,s變量和s2變量地址指向的是不同的對象,所以返回false
  • String s3 = new String("3") + new String("3");創(chuàng)建了兩個對象,一個在堆中的StringObject對象,一個是在堆中的“3”對象,并在常量池中保存“3”對象的引用地址。中間還有2個匿名的new String(“3”)我們不去討論它們。
  • s3.intern();在常量池中尋找與s3變量內(nèi)容相同的對象,沒有發(fā)現(xiàn)“33”對象,將s3對應(yīng)的StringObject對象的地址保存到常量池中,返回StringObject對象的地址。
  • String s4 = "33";使用字面量創(chuàng)建,在常量池尋找是否有相同內(nèi)容的對象,發(fā)現(xiàn)有,返回其地址,也就是StringObject對象的引用地址。
  • System.out.println(s3 == s4);從上面可以分析出,s3變量和s4變量地址指向的是相同的對象,所以返回true。

3. String.intern()的應(yīng)用

在大量字符串讀取賦值的情況下,使用String.intern()會大大的節(jié)省內(nèi)存空間。

static final int MAX = 1000 * 10000;
static final String[] arr = new String[MAX];
public static void main(String[] args) throws Exception {
    Integer[] DB_DATA = new Integer[10];
    Random random = new Random(10 * 10000);
    for (int i = 0; i < DB_DATA.length; i++) {
        DB_DATA[i] = random.nextInt();
    }
 long t = System.currentTimeMillis();
    for (int i = 0; i < MAX; i++) {
        //arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length]));
         arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();
    }
 System.out.println((System.currentTimeMillis() - t) + "ms");
    System.gc();
}

運(yùn)行的參數(shù)是:-Xmx2g -Xms2g -Xmn1500M 上述代碼是一個演示代碼,其中有兩條語句不一樣,一條是使用 intern,一條是未使用 intern。發(fā)現(xiàn)不使用 intern 的代碼生成了1000w 個字符串,占用了大約640m 空間。

使用了 intern 的代碼生成了1345個字符串,占用總空間 133k 左右。其實(shí)通過觀察程序中只是用到了10個字符串,所以準(zhǔn)確計(jì)算后應(yīng)該是正好相差100w 倍。雖然例子有些極端,但確實(shí)能準(zhǔn)確反應(yīng)出 intern 使用后產(chǎn)生的巨大空間節(jié)省。

利用String的不變性,String.intern()方法本質(zhì)就是維持了一個String的常量池,而且池里的String應(yīng)該都是唯一的。這樣,我們便可以利用這種唯一性,來做一些文章了。我們可以利用池里String的對象來做鎖,實(shí)現(xiàn)對資源的控制。比如一個城市的某種資源同一時(shí)間只能一個線程訪問,那就可以把城市名的String對象作為鎖,放到常量池中去,同一時(shí)間只能一個線程獲得。

不當(dāng)?shù)氖褂茫篺astjson 中對所有的 json 的 key 使用了 intern 方法,緩存到了字符串常量池中,這樣每次讀取的時(shí)候就會非??欤蟠鬁p少時(shí)間和空間,而且 json 的 key 通常都是不變的。但是這個地方?jīng)]有考慮到大量的 json key 如果是變化的,那就會給字符串常量池帶來很大的負(fù)擔(dān)。

以上就是String.intern()作用與常量池關(guān)系示例解析的詳細(xì)內(nèi)容,更多關(guān)于String.intern()與常量池關(guān)系的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • mybatis-plus多表查詢操作方法

    mybatis-plus多表查詢操作方法

    這篇文章主要介紹了mybatis-plus多表查詢操作方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2023-12-12
  • SpringBoot與spring security的結(jié)合的示例

    SpringBoot與spring security的結(jié)合的示例

    這篇文章主要介紹了SpringBoot與spring security的結(jié)合的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • java實(shí)現(xiàn)輸出文件夾下某個格式的所有文件實(shí)例代碼

    java實(shí)現(xiàn)輸出文件夾下某個格式的所有文件實(shí)例代碼

    這篇文章主要介紹了java實(shí)現(xiàn)輸出文件夾下某個格式的所有文件,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-06-06
  • 強(qiáng)烈推薦IDEA提高開發(fā)效率的必備插件

    強(qiáng)烈推薦IDEA提高開發(fā)效率的必備插件

    這篇文章主要介紹了強(qiáng)烈推薦IDEA提高開發(fā)效率的必備插件,文中有非常詳細(xì)的圖文示例,對想要提高企業(yè)開發(fā)效率的小伙伴們有非常好的幫助,需要的朋友可以參考下
    2021-04-04
  • java swing 實(shí)現(xiàn)加載自定義的字體

    java swing 實(shí)現(xiàn)加載自定義的字體

    這篇文章主要介紹了java swing 實(shí)現(xiàn)加載自定義的字體,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java 完美判斷中文字符的方法

    Java 完美判斷中文字符的方法

    Java判斷一個字符串是否有中文一般情況是利用Unicode編碼正則來做判斷,但是其實(shí)這個區(qū)間來判斷中文不是非常精確,以下是比較完善的判斷方法
    2013-02-02
  • SpringMVC @ResponseBody 415錯誤處理方式

    SpringMVC @ResponseBody 415錯誤處理方式

    這篇文章主要介紹了SpringMVC @ResponseBody 415錯誤處理方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • SpringBoot實(shí)現(xiàn)緩存組件配置動態(tài)切換的步驟詳解

    SpringBoot實(shí)現(xiàn)緩存組件配置動態(tài)切換的步驟詳解

    現(xiàn)在有多個springboot項(xiàng)目,但是不同的項(xiàng)目中使用的緩存組件是不一樣的,有的項(xiàng)目使用redis,有的項(xiàng)目使用ctgcache,現(xiàn)在需要用同一套代碼通過配置開關(guān),在不同的項(xiàng)目中切換這兩種緩存,本文介紹了SpringBoot實(shí)現(xiàn)緩存組件配置動態(tài)切換的步驟,需要的朋友可以參考下
    2024-07-07
  • Spring Boot集成MyBatis-Plus 自定義攔截器實(shí)現(xiàn)動態(tài)表名切換功能

    Spring Boot集成MyBatis-Plus 自定義攔截器實(shí)現(xiàn)動態(tài)表名切換功能

    本文介紹了如何在SpringBoot項(xiàng)目中集成MyBatis-Plus,并通過自定義攔截器實(shí)現(xiàn)動態(tài)表名切換,此外,還探討了MyBatis攔截器在其他場景中的應(yīng)用,如SQL日志記錄、多租戶數(shù)據(jù)隔離、數(shù)據(jù)權(quán)限控制等,感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • JAVA Static關(guān)鍵字的用法

    JAVA Static關(guān)鍵字的用法

    這篇文章主要介紹了JAVA Static關(guān)鍵字的用法,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07

最新評論