String特性再探究、intern()方法詳解

String基本特性

  • String:字符串,使用一對""引起來表示。
  • String聲明爲final的,不可被繼承
  • String實現了Serializable接口:表示字符串是支持序列化的。實現了Comparable接口:表示String可以比較大小
  • String在jdk8及以前內部定義了final char[] value用於存儲字符串數據。jdk9時改爲byte[]
  • String:代表不可變的字符序列。簡稱:不可變性
  1. 當對字符串重新賦值時,需要重寫指定內存區域賦值,不能使用原有的value進行賦值.
  2. 當對現有的字符串進行連接操作時,也需要重新指定內存區域賦值,不能使用原有的value進行賦值。
  3. 當調用String的replace ()方法修改指定字符或字符串時,也需要重新指定內存區域賦值,不能使用原有的value進行賦值。
  • 通過字面量的方式(區別於new)給一個字符串賦值,此時的字符串值聲明在字符串常量池中。

  • 字符串常量池中是不會存儲相同的字符串的。

  1. String的String Pool是 一個固定大小的Hashtable,默認值大小長度是1009。如果放進String Pool的String非常多, 就會造成Hash衝突嚴重,從而導致鏈表會很長,而鏈表長了後直接會造成的影響就是當調用string. intern時性能會大幅下降。
  2. 使用-XX :StringTableSi ze可設置Str ingTable的長度
  3. 在jdk6中StringTable是固定的,就是1009的長度, 所以如果常量池中的字符串過多就會導致效率下降很快。stringTableSize設置沒有要求
  4. 在jdk7中,StringTable的長度默認值是 60013
  5. Jdk8開始,設置StringTable的長度的話,1009是可設置的最小值。

String的內存分配

  • Java 6及以前,字符串常量池存放在永久代。
  • Java 7中Oracle 的工程師對字符串池的邏輯做了很大的改變,即將字符串常量池的位置調整到Java堆內。
    ➢ 所有的字符串都保存在堆(Heap)中,和其他普通對象一樣,這樣可以讓你在進行調優應用時僅需要調整堆大小就可以了。
    ➢ 字符串常量池概念原本使用得比較多,但是這個改動使得我們有足夠的理由讓我們重新考慮在Java 7中使用String. intern()。
  • Java8元空間,字符串常量在堆
    在這裏插入圖片描述

String拼接操作

  1. 常量與常量的拼接結果在常量池,原理是編譯期優化
  2. 常量池中不會存在相同內容的常量。
  3. 只要其中有一個是變量,結果就在堆中。變量拼接的原理是StringBuilder
  4. 如果拼接的結果調用intern()方法,則主動將常量池中還沒有的字符串對象放入池中,並返回此對象地址。
    在這裏插入圖片描述
    比較效率的一個例題:
    在這裏插入圖片描述

intern()的使用

@NotNull public native String intern();

如果不是用雙引號聲明的String對象,可以使用String提供的intern方法: intern方法會從字符串常量池中查詢當前字符串是否存在,若不存在就會將當前字符串放入常量池中。比如: String myInfo = new String(“I love you”) . intern() ;

也就是說,如果在任意字符串上調用String. intern方法,那麼其返回結果所指向的)那個類實例,必須和直接以常量形式出現的字符串實例完全相同。因此,下列表達式的值必定是true:(“a” + “b” + “c”) . intern() == “abc”

通俗點講,Interned Str ing就是確保字符串在內存裏只有一份拷貝,這樣可以節約內存空間,加快字符串操作任務的執行速度。注意,這個值會被存放在字符串內部池(String Intern Pool) 。
在這裏插入圖片描述

new String(“ab”)會創建幾個對象?

在這裏插入圖片描述

(前提常量池中沒有"ab")
對象1: new關鍵字在堆空間創建的;
對象2: 字符串常量池中的對象“ab”。字節碼指令: ldc

new String(“a”)+new String(“b”)創建幾個對象?

在這裏插入圖片描述

對象1: new StringBuilder()
對象2: new String("a")
對象3: 常量池中的"a"
對象4: new String("b")
對象5: 常量池中的"b"

深入剖析:
StringBuilder的toString():
在這裏插入圖片描述

對象6 : new String( “ab”)
強調一下:由字節碼可知,此時的toString()的調用, 在字符串常量池中,沒有生成"ab"

挺難的一個面試題

public static void main(String[] args){
	Stirng s = new String("1");
	s.intern();//調用此方法之前,字符串常量池中已經存在“1”
	String s2 = "1";
	System.out.println(s==s2);//jdk6:false      jdk7/8:false

	String s3 = new String("1") + new String("1");//s3變量記錄的地址爲:new String("11")
	//執行上一行代碼完畢以後,字符串常量池中,是否存在“11”呢?答案:不存在~(上面的字節碼有證明)這一點很重要
	s3.intern();/*在字符串常量池中生成“11”。 如何理解:
			jdk6:直接在字符串常量池中創建了一個新的對象“11”,也就是新的地址
			jdk7:此時常量池中並沒有直接去創建“11”,而是創建了一個指向堆空間中new String("11")的地址(節省空間)
	 */ 
	String s4 = "11";//s4變量記錄的地址:使用的是上一行代碼執行時,在常量池中生成的“11”的地址
	System.out.println(s3==s4);//jdk6:false    jdk7/8:true
}

在這裏插入圖片描述
在這裏插入圖片描述

//拓展練習:
public static void main(String[] args){
	String s3 = new String("1") + new String("1");//s3變量記錄的地址爲:new String("11")
	//執行上一行代碼完畢以後,字符串常量池中,是否存在“11”呢?答案:不存在~~(同上)
	String s4 = "11";//在字符串常量池中生成對象“11”
	String s5 = s3.intern();//此時字符串常量池已存在“11”了,所以返回的就是常量池中已經創建的“11”地址,對s3的引用沒有任何影響
	//回顧intern():當調用intern方法時,如果池已經包含與equals(Object)方法確定的相當於此String對象的字符串,則返回來自池的字符串。 否則,此String對象將添加到池中,並返回對此String對象的引用。 
	System.out.println(s3==s4);//jdk7/8:false
	System.out.println(s5==s4);//jdk7/8:true
}

總結String的intern()的使用:

  • jdk1.6中,將這個字符串對象嘗試放入串池。
    ➢如果串池中有,則並不會放入。返回已有的串池中的對象的地址
    ➢如果沒有,會把此對象複製一份,放入串池,並返回串池中的對象地址
  • Jdk1.7起,將這個字符串對象嘗試放入串池。
    ➢如果串池中有,則並不會放入。返回已有的串池中的對象的地址
    ➢如果沒有,則會把對象的引用地址複製一份,放入串池,並返回串池中的引用地址

intern()方法練習題

例1

public static void main(String[] args) {
     String s = new String("a") + new String("b");//new String("ab")
     ////執行上一行代碼完畢以後,字符串常量池中,是否存在“11”呢?答案:不存在~(同上)
     String s2 = s.intern();//jdk6:在常量池中創建一個"ab",把這個地址給s2
                            //jdk7以後:字符串池中沒有創建"ab",而是創建指向new String("ab")的地址也就是s的地址

     System.out.println(s2 == s);//jdk6:false     jdk7/8:true
     System.out.println(s2 == "ab");//jdk6:true   jdk7/8:true
     System.out.println(s == "ab");//jdk6:false   jdk7/8:true
 }

在這裏插入圖片描述
在這裏插入圖片描述
稍微變型一點點:
在這裏插入圖片描述

例2

public static void main(String[] args) {
    String s1=new String("ab");//執行完畢以後,在字符串池中會生成ab
    //String s1=new String("a")+new String("b");//執行完畢後,在字符串池中不會生成"ab"
    String intern = s1.intern();
    String s2="ab";
    System.out.println(s1==s2);//jdk7/8:false       s1是new String("ab")的地址
    System.out.println(s2==intern);//jdk7/8:true    s2和intern都是字符串池中"ab"的地址
    System.out.println(s1==intern);//jdk7/8:false   s1是new String("ab")的地址
}

intern()性能

通過下面代碼測試intern()性能,可以發現,使用了intern()的方式,會節省很大內存空間,執行所用時間也大大減少。

String[] arr = new String[10000000];
Integer[] data = new Integer[]{1,2,3,4,5,6,7,8,9,10};
for(int i = 0 ; i < arr.length; i++){
	//方式1
	//arr[i] = new string(String.valueOf(data[i % data. length]));
	//方式2
	arr[i] = new String(String.valueof(data[i % data. 1ength])). intern();
}

方式1:在堆中創建了10000000個String對象,在常量池中創建了“1”,“2”,……“10”,而10000000個String對象一直在被arr引用,所以10000000個String對象不會銷燬。
方式2:在常量池中創建了“1”,“2”,……“10”,使用intern()方法後會時arr[i]直接指向字符串常量池中放的“1”,“2”,……“10”實例地址,而new出的String對象由於沒有被其他對象引用會被銷燬騰出了內存空間。

結論:對於程序中大量存在存在的字符串,尤其其中存在很多重複字符串時,使用intern() 可以節省內存空間。因爲不用在堆中創建對象了。

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