一、 String s="a"+"b"+"c"+"d";【建立1個對象】 數組
常量池(棧):a,b函數
代碼被編譯器在編譯時優化後,至關於直接定義了一個」abcd」的字符串測試
二、 String s = "Hello";s = s + " world"; 【建立了2個對象】優化
常量池(棧):Hello,Helloworldui
這段代碼中,s原先指向一個String對象,內容是 "Hello",而後咱們對s進行了+操做,那麼s所指向的那個對象是否發生了改變呢?答案是沒有。這時,s不指向原來那個對象了,而指向了另外一個 String對象,內容爲"Hello world",原來那個對象還存在於內存之中,只是s這個引用變量再也不指向它了。spa
3、 String s=new String("");【建立2個對象】操作系統
4、String s=new String("a"+"b"); 【建立4個對象】 orm
常量池(棧):a,b,ab對象
堆:ab
5、String s = new String("a") + new String("b");【建立5個對象】 接口
常量池(棧):a,b
堆:a,b,ab(說明:a,b是String類,ab是StringBuilder)
*******************************************************************************
1. String str=new String("aaa");
這行代碼究竟建立了幾個String對象呢?答案是2個,而不是3個。因爲new String("aaa")至關於"aaa"與一個就是建立出來的放在堆時原實例對象,而另外一個就是放在常量池中的 "aaa" 對象,固然這裏的str自己只是一個引用,放在棧裏,用來指向堆中建立出來的對象。
常量池(constant pool)指的是在編譯期被肯定,並被保存在已編譯的.class文件中的一些數據。它包括了關於類、方法、接口等中的常量,也包括字符串常量。
1. String str="aaa";
只建立1個對象。這裏涉及到字符串常量池,在JVM中有一個字符串池,它用來保存不少能夠被共享的String對象,這樣若是咱們在使用一樣字面字符串時,它就使用字符串池中同字面的字符串。固然咱們可使用String對象的intern()方法來訪問String對象在字符串池中所對應的常量對象。
上面這行代碼被執行的時候,JVM先到字符串池中查找,看是否已經存在值爲"aaa"的對象,若是存在,則再也不建立新的對象,直接返回已存在對象的引用;若是不存在,則先建立這個對象,而後把它加入到字符串池中,再將它的引用返回。
1. String str1="aaa";
2. String str2="aaa";
也只建立1個對象。能過上面的解釋這個就更清楚了,在執行第二行代碼時,aaa字符串對象在池中已存在,因此直接返回池中已存在的那個字符串對象。
1. String str="aaa"+"bbb";
仍是隻建立1個對象。因爲常量字符串是在編譯的時候就也被肯定的,又因"aaa"和"bbb"都是常量,所以變量str的值在編譯時就能夠肯定。這行代碼編譯後的與String str="aaabbb";是同樣的,這與咱們平時好像不太同樣啊?通常使用「+」鏈接兩個字符串都會產生另外一個新的字符對象。下面咱們看一下例子就明白了:
1. String str1 = "aaa";
2. String str2 = "bbb";
3. String str3 = "aaabbb";
4.
5. String str4 = "aaa" + "bbb";//不會產生新的字符串對象
6. System.out.println(str3 == str4);//true
7.
8. str4 = str1 + "bbb";//會產生新的字符串對象
9. System.out.println(str3 == str4);//false
10.
11. str4 = str1 + str2;//會產生新的字符串對象
12. System.out.println(str3 == str4);//false
從上面例子咱們就能夠得出:使用「+」鏈接的兩個字符串自己就是字面常量字符串時,若是池中存在這樣鏈接後的字符串,則是不會從新建立對象,而是直接引用池中的字符串對象;若是「+」鏈接的兩字符串中只要有一個不是字面常量串(即定義過的),是會產生新的字符串對象。
凡事也有例外,這個也不例外:若是「+」鏈接的字符串中兩個或一個不是「字面常量」,但若是定義成常量字符串時,狀況又有變化:
1. final String str1 = "aaa";
2. final String str2 = "bbb";
3. String str3 = "aaabbb";
4.
5. /*
6. * 由於str1與str2都定義成了常量,因此編譯時就能肯定,編譯時就會將常量替換,等同於
7. * str4 = "aaa"+"bbb",所以不產生新對象
8. */
9. String str4 = str1 + str2;
10. System.out.println(str3 == str4);//true
但若是先定義final字符串,但未在定義處初始化,而初始化在塊中,以下:
1. //此時str1與str2至關於變量,而不是常,由於塊是在運行時才能肯定,在編譯時不能肯定
2. final static String str1;
3. final static String str2;
4. static {
5. str1 ="aaa";
6. str2 ="bbb";
7. }
8. public static void main(String[] args){
9. String str3 = str1 + str2;
10. String str4 ="aaabbb";
11. System.out.println(str3==str4); //輸出爲false
12. }
13. String str=" ";與String str=new String();
14. str=" "會放入池中,但new String()不會放入池中。
String的intern()方法
「當調用 intern 方法時,若是池已經包含一個等於此 String 對象的字符串(該對象由 equals(Object) 方法肯定),則返回池中的字符串;不然,將此 String 對象添加到池中,而且返回此 String 對象的引用。它遵循對於任何兩個字符串 s 和 t,當且僅當 s.equals(t) 爲 true 時,s.intern() == t.intern() 才爲 true」,這是jdk文檔原文註解。
如今咱們回到最開頭的那個例子,爲何String str=new String("aaa");會產生2個對象?一個是"aaa"又做爲字符串構造函數的參數,但"aaa"本身就是一個字符串,在傳進構造函數前就已建立了一個字符對象,實質上與就比如是第二個實例:String str="aaa"; ,它建立的字符串對象會放入到池中,而且引用的也是池中的那個字符串對象;另外一個就是經過new String()構造函數建立的。因此new String("aaa")會產生兩個對象,也就是說經過此種方式建立字符串對象時,會先將字符串參數對象放入對象池,而後另外建立一個字符串對象。
理解完new String("aaa")爲何會產生兩個對象,咱們再來看看new String(char value[])這樣的方式建立時,又會不會把字符串對象放入到池中呢?答案是不會的。由於傳遞給構造函數的是一個字符數組,而不是像前面傳遞的是一個字面常量字符串參數那樣,將字符串參數自己放入池中。那麼咱們如今若是證實new String(char value[])未將字符串對象放入池中,咱們能夠寫一個簡單的測試,運行時打開XP的任務管理器,查看操做系統的內存使用狀況就能夠初步確認:
1. int size = 10000000;
2. char c[] = new char[size];
3. for (int i = 0; i < size; i++) {
4. c[i] = 'a';
5. }
6. //使用帶字符數組參數構造函數建立字符串時,字符串對象不會放入字符串池
7. String str1 = new String(c);
8. System.out.println("String字符串對象建立完畢...");
9. Thread.sleep(5000);
10. str1.intern();//到裏會看見內存增長
11. System.out.println("第一次調用intern()完畢...");
12. Thread.sleep(5000);
13. str1.intern();//再過5秒將看不到內存增加,由於池中有了,不會再放入,因此內存無變化
14. System.out.println("第二次調用intern()完畢...");
15. Thread.sleep(5000);
因此建立字符串對象放入並放入池中有二種方式:第一種就是直接使用字面常量定義字符串時,如 String str="aaa"; ,str會引用放入池中的對象;第二種就是使用帶字符串參數的字符串構造函數,而且此時傳入的參數值要是字符串常量形式,而不能是變量的形式,也就是說只能是 String str=new String("aaa");形式,而不能是先定義 String s = "aaa",而後再使用 String str=new String(s);來建立對象,new String(s);此時只建立一個對象,但若是池中不存在時咱們可使用intern方法將它放入池中。固然上面放入池中的前提是池中還不存在這些字符串對象。
其實,當咱們仔細研究時,發現放入池中只實質上只存在一種時機,那就是:直接使用字面常量字符串時。上面所說的兩種時機實質上就是直接使用了字面常的字符串而將其放入池中的。
上面多處提到了JVM中的堆棧,下面小結一下各自原做用:
棧用來保存基本類型與對象的引用的,基本型在建立前會查看Stack中是否已經有, 有則指向, 沒有則建立。
String內部是以字符串數組來存儲字符串的,所以能夠認爲與char[]等同, String a= "abc",首先在Heap中創一個對象,再到Stack中找char[]是否存在,有則指向該地址, 無則在Stack中建立數組。
new出來的都是在Heap中,堆是用於存儲對象的。