您好,登錄后才能下訂單哦!
今天小編給大家分享一下Java String中的intern方法如何使用的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
在 JAVA 語言中有8種基本類型和一種比較特殊的類型String。這些類型為了使他們在運行過程中速度更快,更節省內存,都提供了一種常量池(在方法區)的概念。常量池就類似一個JAVA系統級別提供的緩存。8種基本類型的常量池都是系統協調的,String類型的常量池比較特殊。
String的常量池的主要使用方法有兩種:
直接使用雙引號聲明出來的String對象會直接存儲在常量池中。
如果不是用雙引號聲明的String對象,可以使用String提供的intern方法將其放到常量池。
原型:public native String intern();
說明:
從字符串常量池中查詢當前字符串是否存在(通過equals判斷)。
如果存在,返回常量池中的字符串引用。
如果不存在,把這個String對象引用存到常量池,然后返回這個String對象的引用。
返回值:都是返回String變量對應的字符串常量池的引用。
示例
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及以前調用String.intern()
若常量池中有,則返回常量池中這個字符串的引用
若常量池中沒有,則拷貝一份對象,放到常量池(永久代)中;返回值是常量池(永久代)中對應字符串實例的引用。
JDK7及以后調用String.intern()
若常量池中有,則返回常量池中這個字符串的引用
若常量池中沒有,則拷貝一份引用,放到常量池(堆)中;(JDK1.7將String常量池從Perm區移動到了Java Heap區)
例程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); } }
結果
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); } }
上述代碼第二部分有個對調。
結果
jdk6:false false
jdk7:false false
jdk8:false false
下邊圖中:綠色線條代表 String 對象的內容指向。 紅色線條代表地址指向。
例程1與例程2的分析
如上圖所示。首先說一下 jdk6中的情況,在 jdk6中上述的所有打印都是 false 的,因為 jdk6中的常量池是放在 Perm 區中的,Perm區和正常的 JAVA Heap 區域是完全分開的。上面說過如果是使用引號聲明的字符串都是會直接在字符串常量池中生成,而 new出來的 String 對象是放在 JAVA Heap 區域。所以拿一個 JAVA Heap 區域的對象地址和字符串常量池的對象地址進行比較肯定是不相同的,即使調用String.intern方法也是沒有任何關系的。
在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的Perm區的,Perm區是一個類靜態的區域,主要存儲一些加載類的信息,常量池,方法片段等內容,默認大小只有4m,一旦常量池中大量使用 intern 會產生java.lang.OutOfMemoryError:PermGen space錯誤的。在 jdk7 的版本中,字符串常量池已經從Perm區移到正常的Java Heap區域了。為什么要移動,Perm 區域太小是一個主要原因,當然據消息稱jdk8已經直接取消了Perm區域,而新建立了一個元區域。應該是jdk開發者認為Perm區域已經不適合現在 JAVA的發展了。字符串常量池移動到JAVA Heap區域,現在解釋為什么會有上述的打印結果。
例程1的分析
1.String s1 = new String("1");
分析:這行代碼生成了2個對象(常量池中的“1” 和 JavaHeap 中的字符串對象)。s.intern(); 這一句是 s1 對象去常量池中尋找后發現 “1” 已經在常量池里了。
此時s1指向的是Java Heap中的字符串對象。
2.String s2 = "1";
分析:這行代碼生成一個 s2的引用指向常量池中的“1”對象。 結果就是 s1 和 s2 的引用地址不同。
3.String s3 = new String("1") + new String("1");
分析:這行代碼生成了2個對象(字符串常量池中的“1” 和 Java Heap中的 s3 引用指向的對象“11”(中間還有2個匿名的new String("1")我們不討論它)。
此時s3 是Java Heap中的字符串對象的引用,對象內容是”11″,此時常量池中是沒有 “11”對象的。
4.s3.intern();
分析:這行代碼將 s3中的"11"字符串放入String 常量池中,因為此時常量池中不存在"11"字符串,因此常規做法是跟 jdk6 圖中表示的那樣,在常量池中生成一個"11"的對象,關鍵點是 jdk7 中常量池不在Perm區域,而是在堆中了。常量池中不需再存儲一份對象了,可以直接存儲堆中的引用。這份引用指向s3引用的對象。 也就是說引用地址是相同的。
此時,s3是Java Heap中的字符串對象的引用,對象內容是”11″,此時常量池中是有 “11”對象,它保存的就是s3引用地址。
5.String s4 = "11";
這行代碼”11″是顯式聲明的,因此會直接去常量池中創建,創建時發現已經有這個對象了。
此時:s4 == 常量池的“11”對象引用 == s3引用對象的引用
例程2的分析
String s1 = new String("1");
s1.intern();
String s2 = "1";
分析:s1.intern();,這一句往后放也不會有什么影響了,因為對象池中在執行第一句代碼String s = new String("1");的時候已經生成“1”對象了。下邊的s2聲明都是直接從常量池中取地址引用的。 s1 和 s2 的引用地址是不會相等的。
String s3 = new String("1") + new String("1");
分析:這行代碼生成了2個對象(字符串常量池中的“1” 和 Java Heap中的 s3 引用指向的對象“11”(中間還有2個匿名的new String("1")我們不討論它)。
此時s3 是Java Heap中的字符串對象的引用,對象內容是”11″,此時常量池中是沒有 “11”對象的。
String s4 = "11";
分析:聲明 s4 的時候常量池中是不存在“11”對象的,執行完后,s4是常量池里“11“對象的引用。
s3.intern();
分析:此時常量池中“11”對象已經存在了,不會有任何操作,s3仍然是堆中String對象的引用。因此 s3 != s4
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(); } }
上述代碼是一個演示代碼,其中有兩條語句不一樣,一條是使用 intern,一條是未使用 intern。
運行的參數是:-Xmx2g -Xms2g -Xmn1500M
不用intern
2160ms
使用intern
826ms
通過上述結果,我們發現不使用 intern 的代碼生成了1000w 個字符串,占用了大約640m 空間。 使用了 intern 的代碼生成了1345個字符串,占用總空間 133k 左右。其實通過觀察程序中只是用到了10個字符串,所以準確計算后應該是正好相差100w 倍。雖然例子有些極端,但確實能準確反應出 intern 使用后產生的巨大空間節省。
細心的同學會發現使用了 intern 方法后時間上有了一些增長。這是因為程序中每次都是用了 new String 后, 然后又進行 intern 操作的耗時時間,這一點如果在內存空間充足的情況下確實是無法避免的,但我們平時使用時,內存空間肯定不是無限大的,不使用 intern占用空間導致 jvm 垃圾回收的時間是要遠遠大于這點時間的。 畢竟這里使用了1000w次intern 才多出來1秒鐘多的時間。
以上就是“Java String中的intern方法如何使用”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。