String的intern()詳解

1.JDK中註釋

在這裏插入圖片描述
返回字符串對象的canonical表示。由String類私有維護的字符串池,最初爲空。
當調用intern方法時,如果池已經包含此字符串(equals確定),則返回池中字符串。否則,將此String對象添加到池中並且返回該String對象的引用。
對於任何兩個字符串s和t,當且僅當s.equals(t)爲真時,s.intern()==t.intern()才爲真。
所有字符串字面值和字符串常量表達式都是intern。
返回值:與該String對象有相同內容的對象,保證來自字符串唯一的池。

從註釋可以看出來,intern有倆種情況
1.如果存在
   判斷存在內容是引用還是常量,
    如果是引用,
     返回引用地址指向堆空間對象,
    如果是常量,
     直接返回常量池常量
  2.如果不存在,
   將當前對象引用複製到常量池,並且返回的是當前對象的引用

2.intern在常量池中存儲常量還是引用

1.在堆上創建對象,在常量池創建引用
String a = new String("A") + new String("B");//在堆上創建對象AB
// a.intern();//將該對象AB的引用保存到常量池上
System.out.println(a == a.intern());//true
2.在常量池創建常量
String b="abc";//在常量池中創建常量abc
String c="abc";//直接返回常量abc
System.out.println(b==c);
3.在堆上創建對象,在常量池上創建常量
 String a3 = new String("AA");//在堆上創建對象AA,在常量池中創建常量AA
4.對象是否共存

3.關於intern數據存儲

它的大體實現結構就是: JAVA 使用 jni 調用c++實現的StringTable的intern方法,
StringTable的intern方法跟Java中的HashMap的實現是差不多的, 只是不能自動擴容。默認大小是1009。
要注意的是,String的String Pool是一個固定大小的Hashtable,默認值大小長度是1009,如果放進String
Pool的String非常多,就會造成Hash衝突嚴重,從而導致鏈表會很長,而鏈表長了後直接會造成的影響就是當調用String.intern時性能會大幅下降(因爲要一個一個找)。

jdk6中StringTable是固定的,就是1009的長度,所以如果常量池中的字符串過多就會導致效率下降很快。在jdk7中,StringTable的長度可以通過一個參數指定:
-XX:StringTableSize=99991 相信很多 JAVA 程序員都做做類似 String s = new String(“abc”)這個語句創建了幾個對象的題目。
這種題目主要就是爲了考察程序員對字符串對象的常量池掌握與否。上述的語句中是創建了2個對象,第一個對象是”abc”字符串存儲在常量池中,第二個對象在JAVA
Heap中的 String 對象。

1.JDK8以後StringTable常量池默認大小

關於StringTableSize,使用虛擬機參數 -XX:+PrintFlagsInitial或者
-XX:+PrintFlagsFinal看看Java8設置的默認值:
uintx StringTableSize = 60013 {product}
鏈接法處理衝突:
template <MEMFLAGS F> inline void BasicHashtable<F>::add_entry(int index, BasicHashtableEntry<F>* entry) {
  entry->set_next(bucket(index));
  _buckets[index].set_entry(entry);
  ++_number_of_entries;
}

StringTable的intern方法跟Java中的HashMap的實現是差不多的, 只是不能自動擴容。默認大小是60013 。如果放進String Pool的String非常多,就會造成Hash衝突嚴重,從而導致鏈表會很長,而鏈表長了後直接會造成的影響就是當調用String.intern時性能會大幅下降。

4.分析與總結

Java7之後的字符串常量值和String.intern()機制:
1)對於字符串字面值和字符串常量表達式,都會intern,添加到常量池,例如new String(“1”);就有兩個操作:新建堆對象String;將"1"添加到常量池
2)對於String s4 = "11"這種,會在字符串常量池中新建"11"對象
3)String.intern(),因爲已經轉移到了堆中,所以沒有必要再在常量池存儲一份對象拷貝,所以直接存儲堆中String對象的引用。
intern()方法目的是提示JVM把相應字符串緩存起來,以備重複使用。使用JDK6這種歷史版本,不推薦大量使用intern。因爲緩存的字符串存在PermGen永久代裏,這個空間是很有限的,也基本不會被FullGC之外的GC照顧到,所以如果使用不當,會出現OOM。
後續版本,該緩存放在堆中。並且JDK8中永久代被MetaSpace替代。
intern()是一種顯式排重機制。副作用:
1)顯式調用麻煩
2)很難保證效率,很難清楚地預計字符串重複情況
在Oracle JDK 8u20後,推出了一個新特性,G1 GC下的字符串排重。將相同數據的字符串指向同一份數據,是JVM底層的改變,並不需要Java類庫做什麼修改。
指定使用G1 GC,並開啓-XX:+UseStringDeduplication。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章