文章目錄
String基本特性
- String:字符串,使用一對""引起來表示。
- String聲明爲final的,不可被繼承
- String實現了Serializable接口:表示字符串是支持序列化的。實現了Comparable接口:表示String可以比較大小
- String在jdk8及以前內部定義了final char[] value用於存儲字符串數據。jdk9時改爲byte[]
- String:代表不可變的字符序列。簡稱:不可變性
- 當對字符串重新賦值時,需要重寫指定內存區域賦值,不能使用原有的value進行賦值.
- 當對現有的字符串進行連接操作時,也需要重新指定內存區域賦值,不能使用原有的value進行賦值。
- 當調用String的replace ()方法修改指定字符或字符串時,也需要重新指定內存區域賦值,不能使用原有的value進行賦值。
-
通過字面量的方式(區別於new)給一個字符串賦值,此時的字符串值聲明在字符串常量池中。
-
字符串常量池中是不會存儲相同的字符串的。
- String的String Pool是
一個固定大小的Hashtable
,默認值大小長度是1009。如果放進String Pool的String非常多, 就會造成Hash衝突嚴重,從而導致鏈表會很長,而鏈表長了後直接會造成的影響就是當調用string. intern時性能會大幅下降。- 使用-XX :StringTableSi ze可設置Str ingTable的長度
- 在jdk6中StringTable是固定的,就是1009的長度, 所以如果常量池中的字符串過多就會導致效率下降很快。stringTableSize設置沒有要求
- 在jdk7中,StringTable的長度默認值是 60013
- Jdk8開始,設置StringTable的長度的話,1009是可設置的最小值。
String的內存分配
- Java 6及以前,字符串常量池存放在永久代。
- Java 7中Oracle 的工程師對字符串池的邏輯做了很大的改變,即將字符串常量池的位置調整到Java堆內。
➢ 所有的字符串都保存在堆(Heap)中,和其他普通對象一樣,這樣可以讓你在進行調優應用時僅需要調整堆大小就可以了。
➢ 字符串常量池概念原本使用得比較多,但是這個改動使得我們有足夠的理由讓我們重新考慮在Java 7中使用String. intern()。 - Java8元空間,字符串常量在堆
String拼接操作
- 常量與常量的拼接結果在常量池,原理是編譯期優化
- 常量池中不會存在相同內容的常量。
- 只要其中有一個是變量,結果就在堆中。變量拼接的原理是StringBuilder
- 如果拼接的結果調用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() 可以節省內存空間。因爲不用在堆中創建對象了。