溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java?String中intern方法怎么使用

發(fā)布時(shí)間:2022-05-19 14:54:41 來(lái)源:億速云 閱讀:136 作者:iii 欄目:開發(fā)技術(shù)

今天小編給大家分享一下Java String中intern方法怎么使用的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。

    常量池簡(jiǎn)介

    在 JAVA 語(yǔ)言中有8種基本類型和一種比較特殊的類型String。這些類型為了使他們?cè)谶\(yùn)行過(guò)程中速度更快,更節(jié)省內(nèi)存,都提供了一種常量池(在方法區(qū))的概念。常量池就類似一個(gè)JAVA系統(tǒng)級(jí)別提供的緩存。8種基本類型的常量池都是系統(tǒng)協(xié)調(diào)的,String類型的常量池比較特殊。

    String的常量池的主要使用方法有兩種:

    直接使用雙引號(hào)聲明出來(lái)的String對(duì)象會(huì)直接存儲(chǔ)在常量池中。

    如果不是用雙引號(hào)聲明的String對(duì)象,可以使用String提供的intern方法將其放到常量池。

    intern方法簡(jiǎn)介(JDK7)

    原型:public native String intern();

    說(shuō)明:

    從字符串常量池中查詢當(dāng)前字符串是否存在(通過(guò)equals判斷)。

    • 如果存在,返回常量池中的字符串引用。

    • 如果不存在,把這個(gè)String對(duì)象引用存到常量池,然后返回這個(gè)String對(duì)象的引用。

    返回值:都是返回String變量對(duì)應(yīng)的字符串常量池的引用。

    示例

    package com.example;
     
    public class Demo {
        public static void main(String argv[]) {
            String s = "test";
            System.out.println(s == s.intern());
        }
    }

    JDK6及之前:輸出false

    JDK7及之后:輸出true

    原理(JDK6與JDK7)

    常量池里的字符串的由來(lái)

    JDK6及以前調(diào)用String.intern()

    • 若常量池中有,則返回常量池中這個(gè)字符串的引用

    • 若常量池中沒(méi)有,則拷貝一份對(duì)象,放到常量池(永久代)中;返回值是常量池(永久代)中對(duì)應(yīng)字符串實(shí)例的引用。

    JDK7及以后調(diào)用String.intern()

    • 若常量池中有,則返回常量池中這個(gè)字符串的引用

    • 若常量池中沒(méi)有,則拷貝一份引用,放到常量池(堆)中;(JDK1.7將String常量池從Perm區(qū)移動(dòng)到了Java Heap區(qū))

    例程測(cè)試

    例程1: 

    package org.example.a;
     
    public class Demo {
        public static void main(String argv[]) {
            String s1 = new String("1");
            s1.intern();
            String s2 = "1";
            System.out.println(s1 == s2);
     
            String s3 = new String("1") + new String("1");
            s3.intern();
            String s4 = "11";
            System.out.println(s3 == s4);
        }
    }

    結(jié)果

    jdk6:false false
    jdk7:false true
    jdk8:false true

    例程2: 

    package org.example.a;
     
    public class Demo {
        public static void main(String argv[]) {
            String s1 = new String("1");
            s1.intern();
            String s2 = "1";
            System.out.println(s1 == s2);
     
            String s3 = new String("1") + new String("1");
            String s4 = "11";
            s3.intern();
            System.out.println(s3 == s4);
        }
    }

    上述代碼第二部分有個(gè)對(duì)調(diào)。

    結(jié)果

    jdk6:false false
    jdk7:false false
    jdk8:false false

    例程分析

    下邊圖中:綠色線條代表 String 對(duì)象的內(nèi)容指向。 紅色線條代表地址指向。

    jdk1.6

    例程1與例程2的分析

    Java?String中intern方法怎么使用

    如上圖所示。首先說(shuō)一下 jdk6中的情況,在 jdk6中上述的所有打印都是 false 的,因?yàn)?jdk6中的常量池是放在 Perm 區(qū)中的,Perm區(qū)和正常的 JAVA Heap 區(qū)域是完全分開的。上面說(shuō)過(guò)如果是使用引號(hào)聲明的字符串都是會(huì)直接在字符串常量池中生成,而 new出來(lái)的 String 對(duì)象是放在 JAVA Heap 區(qū)域。所以拿一個(gè) JAVA Heap 區(qū)域的對(duì)象地址和字符串常量池的對(duì)象地址進(jìn)行比較肯定是不相同的,即使調(diào)用String.intern方法也是沒(méi)有任何關(guān)系的。

    jdk1.7

    在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的Perm區(qū)的,Perm區(qū)是一個(gè)類靜態(tài)的區(qū)域,主要存儲(chǔ)一些加載類的信息,常量池,方法片段等內(nèi)容,默認(rèn)大小只有4m,一旦常量池中大量使用 intern 會(huì)產(chǎn)生java.lang.OutOfMemoryError:PermGen space錯(cuò)誤的。在 jdk7 的版本中,字符串常量池已經(jīng)從Perm區(qū)移到正常的Java Heap區(qū)域了。為什么要移動(dòng),Perm 區(qū)域太小是一個(gè)主要原因,當(dāng)然據(jù)消息稱jdk8已經(jīng)直接取消了Perm區(qū)域,而新建立了一個(gè)元區(qū)域。應(yīng)該是jdk開發(fā)者認(rèn)為Perm區(qū)域已經(jīng)不適合現(xiàn)在 JAVA的發(fā)展了。字符串常量池移動(dòng)到JAVA Heap區(qū)域,現(xiàn)在解釋為什么會(huì)有上述的打印結(jié)果。

    例程1的分析

    Java?String中intern方法怎么使用

    1.String s1 = new String("1"); 

    分析:這行代碼生成了2個(gè)對(duì)象(常量池中的“1” 和 JavaHeap 中的字符串對(duì)象)。s.intern(); 這一句是 s1 對(duì)象去常量池中尋找后發(fā)現(xiàn) “1” 已經(jīng)在常量池里了。

    此時(shí)s1指向的是Java Heap中的字符串對(duì)象。

    2.String s2 = "1"; 

    分析:這行代碼生成一個(gè) s2的引用指向常量池中的“1”對(duì)象。 結(jié)果就是 s1 和 s2 的引用地址不同。

    3.String s3 = new String("1") + new String("1");

    分析:這行代碼生成了2個(gè)對(duì)象(字符串常量池中的“1” 和 Java Heap中的 s3 引用指向的對(duì)象“11”(中間還有2個(gè)匿名的new String("1")我們不討論它)。
    此時(shí)s3 是Java Heap中的字符串對(duì)象的引用,對(duì)象內(nèi)容是”11″,此時(shí)常量池中是沒(méi)有 “11”對(duì)象的。

    4.s3.intern();

    分析:這行代碼將 s3中的"11"字符串放入String 常量池中,因?yàn)榇藭r(shí)常量池中不存在"11"字符串,因此常規(guī)做法是跟 jdk6 圖中表示的那樣,在常量池中生成一個(gè)"11"的對(duì)象,關(guān)鍵點(diǎn)是 jdk7 中常量池不在Perm區(qū)域,而是在堆中了。常量池中不需再存儲(chǔ)一份對(duì)象了,可以直接存儲(chǔ)堆中的引用。這份引用指向s3引用的對(duì)象。 也就是說(shuō)引用地址是相同的。

    此時(shí),s3是Java Heap中的字符串對(duì)象的引用,對(duì)象內(nèi)容是”11″,此時(shí)常量池中是有 “11”對(duì)象,它保存的就是s3引用地址。

    5.String s4 = "11"; 

    這行代碼”11″是顯式聲明的,因此會(huì)直接去常量池中創(chuàng)建,創(chuàng)建時(shí)發(fā)現(xiàn)已經(jīng)有這個(gè)對(duì)象了。

    此時(shí):s4 == 常量池的“11”對(duì)象引用 == s3引用對(duì)象的引用

    例程2的分析

    Java?String中intern方法怎么使用

    String s1 = new String("1");

    s1.intern();

    String s2 = "1";

    分析:s1.intern();,這一句往后放也不會(huì)有什么影響了,因?yàn)閷?duì)象池中在執(zhí)行第一句代碼String s = new String("1");的時(shí)候已經(jīng)生成“1”對(duì)象了。下邊的s2聲明都是直接從常量池中取地址引用的。 s1 和 s2 的引用地址是不會(huì)相等的。

    String s3 = new String("1") + new String("1");

    分析:這行代碼生成了2個(gè)對(duì)象(字符串常量池中的“1” 和 Java Heap中的 s3 引用指向的對(duì)象“11”(中間還有2個(gè)匿名的new String("1")我們不討論它)。

    此時(shí)s3 是Java Heap中的字符串對(duì)象的引用,對(duì)象內(nèi)容是”11″,此時(shí)常量池中是沒(méi)有 “11”對(duì)象的。

    String s4 = "11";

    分析:聲明 s4 的時(shí)候常量池中是不存在“11”對(duì)象的,執(zhí)行完后,s4是常量池里“11“對(duì)象的引用。

    s3.intern(); 

    分析:此時(shí)常量池中“11”對(duì)象已經(jīng)存在了,不會(huì)有任何操作,s3仍然是堆中String對(duì)象的引用。因此 s3 != s4

    應(yīng)用實(shí)例

    package org.example.a;
     
    import java.util.Random;
     
    public class Demo {
        static final  int MAX = 1000 * 10000;
        static final String[] arr = new String[MAX];
        public static void main(String argv[]) {
            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();
        }
    }

    上述代碼是一個(gè)演示代碼,其中有兩條語(yǔ)句不一樣,一條是使用 intern,一條是未使用 intern。

    運(yùn)行的參數(shù)是:-Xmx2g -Xms2g -Xmn1500M

    不用intern

    2160ms

    Java?String中intern方法怎么使用

    使用intern

    826ms

    Java?String中intern方法怎么使用

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

    細(xì)心的同學(xué)會(huì)發(fā)現(xiàn)使用了 intern 方法后時(shí)間上有了一些增長(zhǎng)。這是因?yàn)槌绦蛑忻看味际怯昧?new String 后, 然后又進(jìn)行 intern 操作的耗時(shí)時(shí)間,這一點(diǎn)如果在內(nèi)存空間充足的情況下確實(shí)是無(wú)法避免的,但我們平時(shí)使用時(shí),內(nèi)存空間肯定不是無(wú)限大的,不使用 intern占用空間導(dǎo)致 jvm 垃圾回收的時(shí)間是要遠(yuǎn)遠(yuǎn)大于這點(diǎn)時(shí)間的。 畢竟這里使用了1000w次intern 才多出來(lái)1秒鐘多的時(shí)間。

    以上就是“Java String中intern方法怎么使用”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI