Java技術——你真的瞭解String類的intern()方法嗎(jdk1.6和1.7之後不一樣!!)(String類的intern看這一篇就足夠了)
0.引言
什麼都先不說,先看下面這個引入的例子:
- String str1 = new String("SEU")+ new String("Calvin");
- System.out.println(str1.intern() == str1);
- System.out.println(str1 == "SEUCalvin");
本人JDK版本1.8,輸出結果為:
- true
- true
- String str2 = "SEUCalvin";//新加的一行程式碼,其餘不變
- String str1 = new String("SEU")+ new String("Calvin");
- System.out.println(str1.intern() == str1);
- System.out.println(str1 == "SEUCalvin");
- false
- false
是不是感覺莫名其妙,新定義的str2好像和str1沒有半毛錢的關係,怎麼會影響到有關str1的輸出結果呢?其實這都是intern()方法搞的鬼!看完這篇文章,你就會明白。o(∩_∩)o
說實話我本來想總結一篇Android記憶體洩漏的文章的,查閱了很多資料,發現不得不從Java的OOM講起,講Java的OOM又不得不講Java的虛擬機器架構。如果不瞭解JVM的同學可以檢視此篇 JVM——Java虛擬機器架構。(這篇文章已經被我修改過N多次了,個人感覺還是挺全面清晰的,每次看都會有新的理解。)
在JVM架構一文中也有介紹,在JVM執行時資料區中的方法區有一個常量池,但是發現在JDK1.6以後常量池被放置在了堆空間,因此常量池位置的不同影響到了String的intern()方法的表現。深入瞭解後發現還是值得寫下來記錄一下的。
1.為什麼要介紹intern()方法
intern()方法設計的初衷,就是重用String物件,以節省記憶體消耗。這麼說可能有點抽象,那麼就用例子來證明。
- static final int MAX = 100000;
- static final String[] arr = new String[MAX];
- public static void main(String[] args) throws Exception {
- //為長度為10的Integer陣列隨機賦值
- Integer[] sample = new Integer[10];
- Random random = new Random(1000);
- for (int i = 0; i < sample.length; i++) {
- sample[i] = random.nextInt();
- }
- //記錄程式開始時間
- long t = System.currentTimeMillis();
- //使用/不使用intern方法為10萬個String賦值,值來自於Integer陣列的10個數
- for (int i = 0; i < MAX; i++) {
- arr[i] = new String(String.valueOf(sample[i % sample.length]));
- //arr[i] = new String(String.valueOf(sample[i % sample.length])).intern();
- }
- System.out.println((System.currentTimeMillis() - t) + "ms");
- System.gc();
- }
這個例子也比較簡單,就是為了證明使用intern()比不使用intern()消耗的記憶體更少。
先定義一個長度為10的Integer陣列,並隨機為其賦值,在通過for迴圈為長度為10萬的String物件依次賦值,這些值都來自於Integer陣列。兩種情況分別執行,可通過Window ---> Preferences --> Java --> Installed JREs設定JVM啟動引數為-agentlib:hprof=heap=dump,format=b,將程式執行完後的hprof置於工程目錄下。再通過MAT外掛檢視該hprof檔案。
兩次實驗結果如下:
從執行結果來看,不使用intern()的情況下,程式生成了101762個String物件,而使用了intern()方法時,程式僅生成了1772個String物件。自然也證明了intern()節省記憶體的結論。
細心的同學會發現使用了intern()方法後程式執行時間有所增加。這是因為程式中每次都是用了new String後又進行intern()操作的耗時時間,但是不使用intern()佔用記憶體空間導致GC的時間是要遠遠大於這點時間的。
2.深入認識intern()方法
JDK1.7後,常量池被放入到堆空間中,這導致intern()函式的功能不同,具體怎麼個不同法,且看看下面程式碼:
- String s = new String("1");
- s.intern();
- String s2 = "1";
- System.out.println(s == s2);
- String s3 = new String("1") + new String("1");
- s3.intern();
- String s4 = "11";
- System.out.println(s3 == s4);
輸出結果為:
- JDK1.6以及以下:false false
- JDK1.7以及以上:false true
再分別調整上面程式碼2.3行、7.8行的順序:
- String s = new String("1");
- String s2 = "1";
- s.intern();
- System.out.println(s == s2);
- String s3 = new String("1") + new String("1");
- String s4 = "11";
- s3.intern();
- System.out.println(s3 == s4);
輸出結果為:
- JDK1.6以及以下:false false
- JDK1.7以及以上:false false
下面依據上面程式碼對intern()方法進行分析:
2.1 JDK1.6
在JDK1.6中所有的輸出結果都是 false,因為JDK1.6以及以前版本中,常量池是放在 Perm 區(屬於方法區)中的,熟悉JVM的話應該知道這是和堆區完全分開的。
使用引號宣告的字串都是會直接在字串常量池中生成的,而 new 出來的 String 物件是放在堆空間中的。所以兩者的記憶體地址肯定是不相同的,即使呼叫了intern()方法也是不影響的。如果不清楚String類的“==”和equals()的區別可以檢視我的這篇博文Java面試——從Java堆、棧角度比較equals和==的區別。
intern()方法在JDK1.6中的作用是:比如String s = new String("SEU_Calvin"),再呼叫s.intern(),此時返回值還是字串"SEU_Calvin",表面上看起來好像這個方法沒什麼用處。但實際上,在JDK1.6中它做了個小動作:檢查字串池裡是否存在"SEU_Calvin"這麼一個字串,如果存在,就返回池裡的字串;如果不存在,該方法會把"SEU_Calvin"新增到字串池中,然後再返回它的引用。然而在JDK1.7中卻不是這樣的,後面會討論。
2.2 JDK1.7
針對JDK1.7以及以上的版本,我們將上面兩段程式碼分開討論。先看第一段程式碼的情況:
再把第一段程式碼貼一下便於檢視:
- String s = new String("1");
- s.intern();
- String s2 = "1";
- System.out.println(s == s2);
- String s3 = new String("1") + new String("1");
- s3.intern();
- String s4 = "11";
- System.out.println(s3 == s4);
String s = newString("1"),生成了常量池中的“1” 和堆空間中的字串物件。
s.intern(),這一行的作用是s物件去常量池中尋找後發現"1"已經存在於常量池中了。
String s2 = "1",這行程式碼是生成一個s2的引用指向常量池中的“1”物件。
結果就是 s 和 s2 的引用地址明顯不同。因此返回了false。
String s3 = new String("1") + newString("1"),這行程式碼在字串常量池中生成“1” ,並在堆空間中生成s3引用指向的物件(內容為"11")。注意此時常量池中是沒有 “11”物件的。
s3.intern(),這一行程式碼,是將 s3中的“11”字串放入 String 常量池中,此時常量池中不存在“11”字串,JDK1.6的做法是直接在常量池中生成一個 "11" 的物件。
但是在JDK1.7中,常量池中不需要再儲存一份物件了,可以直接儲存堆中的引用。這份引用直接指向 s3 引用的物件,也就是說s3.intern() ==s3會返回true。
String s4 = "11", 這一行程式碼會直接去常量池中建立,但是發現已經有這個物件了,此時也就是指向 s3 引用物件的一個引用。因此s3 == s4返回了true。
下面繼續分析第二段程式碼:
再把第二段程式碼貼一下便於檢視:
- String s = new String("1");
- String s2 = "1";
- s.intern();
- System.out.println(s == s2);
- String s3 = new String("1") + new String("1");
- String s4 = "11";
- s3.intern();
- System.out.println(s3 == s4);
String s = new String("1"),生成了常量池中的“1” 和堆空間中的字串物件。
String s2 = "1",這行程式碼是生成一個s2的引用指向常量池中的“1”物件,但是發現已經存在了,那麼就直接指向了它。(因為new的時候會執行兩個操作,產生兩個物件)
s.intern(),這一行在這裡就沒什麼實際作用了。因為"1"已經存在了。
結果就是 s 和 s2 的引用地址明顯不同。因此返回了false。
String s3 = new String("1") + newString("1"),這行程式碼在字串常量池中僅僅只是生成了“1” ,並在堆空間中生成s3引用指向的物件(內容為"11")。注意此時常量池中是沒有 “11”物件的。
String s4 = "11", 這一行程式碼會直接去生成常量池中的"11"。
s3.intern(),這一行在這裡就沒什麼實際作用了。因為"11"已經存在了。
結果就是 s3 和 s4 的引用地址明顯不同。因此返回了false。
3 總結
終於要做Ending了。現在再來看一下開篇給的引入例子,是不是就很清晰了呢。
- String str1 = new String("SEU") + new String("Calvin");
- System.out.println(str1.intern() == str1);
- System.out.println(str1 == "SEUCalvin");
str1.intern() == str1就是上面例子中的情況,str1.intern()發現常量池中不存在“SEUCalvin”(此時常量池只是存在“SEU”和“Calvin”這兩個),因此指向了str1。 "SEUCalvin"在常量池中建立時,也就直接指向了str1了。兩個都返回true就理所當然啦。
那麼第二段程式碼呢:- String str2 = "SEUCalvin";//新加的一行程式碼,其餘不變
- String str1 = new String("SEU")+ new String("Calvin");
- System.out.println(str1.intern() == str1);
- System.out.println(str1 == "SEUCalvin");
好了,本篇對intern的作用以及在JDK1.6和1.7中的實現原理的介紹就到此為止了。希望能給你帶來幫助。
來自:http://blog.csdn.net/seu_calvin/article/details/52291082
轉載自:https://blog.csdn.net/baidu_31657889/article/details/52315902
相關文章
- Java -- String的intern方法Java
- java String的equals,intern方法Java
- 聊聊java String的internJava
- Java String.intern()Java
- String的intern方法使用場景
- 瞭解Java中的鎖,看這一篇就夠了!Java
- 瞭解 MongoDB 看這一篇就夠了MongoDB
- 瞭解SSL證書,看這一篇就夠了!!
- java基礎:String — 字串常量池與intern(二)Java字串
- [翻譯]瞭解NodeJS看這一篇就夠了NodeJS
- Java 集合看這一篇就夠了Java
- 幾張圖輕鬆理解String.intern()
- ava String 物件,你真的瞭解了嗎?物件
- 深入理解 Java String#intern() 記憶體模型Java記憶體模型
- Java String類的replaceAll方法Java
- ActiveMq 之JMS 看這一篇就夠了MQ
- 瞭解HandlerThread這一篇就夠了thread
- 深入談談String.intern()在JVM的實現JVM
- 通過反編譯深入理解Java String及intern編譯Java
- 【JavaScript基礎】你真正瞭解如今的Js陣列嗎,看這篇就(Go)夠了JavaScriptJS陣列Go
- 真的懂Java的String嗎?Java
- Java中String類的常用方法Java
- Git 看這一篇就夠了Git
- 手寫Promise看著一篇就足夠了Promise
- 前端er瞭解GraphQL,看這篇就夠了前端
- 學Mybatis,入門看這一篇就夠你學的了!MyBatis
- 使用String.intern減少記憶體使用記憶體
- 你真的瞭解JAVA中物件和類、this、super和static關鍵字嗎Java物件
- toString().intern()中的intern()中的作用和使用
- MySQL,你只需看這一篇文章就夠了!MySql
- Object類和String類equals方法的區別Object
- Flutter DataTable 看這一篇就夠了Flutter
- 代理模式看這一篇就夠了模式
- 快速瞭解Java多執行緒,有這一篇就夠了Java執行緒
- Java之String類的使用細節Java
- 這一篇就夠啦,帶你瞭解MySQL的常用技巧MySql
- 瞭解DMAIC , 這篇文章足夠了!AI
- Java String類Java