Java中字串常見題之String相關
大家好,小弟季白,之前一直在簡書中釋出部落格,可能是因為感受到了Csdn內強大的學習氛圍,所以也加入了Csdn,成為其中的一名筆者,哈哈。
小弟是一名Android學習者,這是我在簡書釋出的Android相關文章,希望大家多多給予意見:點選開啟連結
今天給大家帶來的是在面試中經常被問到的一道題:
無論在Java還是Android中,String是一個很常見的類,但是大家真的很瞭解嗎,我這裡有幾個題:
1.
String str1 = "abc";
String str2 = new String("abc");
這兩種建立String物件的方法有什麼不同?
2.
String s = "a" + "b" + "c" + "d";
這裡面一共建立了多少物件?
這兩道題昨天給筆者搞得是一臉懵逼,後來一聽這是一道很經典的面試題,就趕快查閱各種資料,現在已經解決了。
在解決這兩個題之前,我們先來明確幾個知識點,相信把這幾個知識點弄完之後再回頭看這兩個題,就很簡單了:
1.引用在棧記憶體中儲存,物件在堆記憶體中儲存。
2.String物件不可變。
3.String建立物件的形式:
4.字串常量池的意義:
5.intern()方法使用。
我們首先來開第一個:
引用在棧記憶體中儲存,物件在堆記憶體中儲存。
這個是我粗略畫的一張圖,這張圖可能不是很準確,但是我只想表達一個意思,我們的棧記憶體,存放的是我們物件的引用和我們基本資料型別的值。而堆記憶體中存放的是我們的物件。就是這麼簡單。
為什麼說String物件不可變
這裡我們用一下大佬的圖:
如果用程式碼表示上面的圖,那就是:
String s = "abcd";
s = s + "el";
如果沒有上面這張圖,大家可能會覺得我們只是給s物件後面加了一個el,這不還是原來的s嗎?不,我們說的String物件不可變就是這個意思,當我們給s再加上el時候,新的字串abcdel被存放到了一個新的記憶體中。已經不是原來的記憶體了。所以說String物件不可變,如果變了,那就已經變成了一個新的物件。
那說到這裡大家可能會返回去看我寫的第二個問題:按你的說法這不就7個物件嗎?abcd各佔一個,每次+到一起都要重新生成物件,一共生成了7個物件。話是沒錯,但是我想說這道題說多解,等我們講完了下面這兩個知識點,這道題就完全解開了。
String建立物件的形式:
正如我們上面看到的,String有兩種建立物件的形式:
1.字面量形式:
String str = "asd";
2.標準的new形式:
String str = new String("asd");
字串常量池的意義:
字串常量池,又稱為字串在字面量池。大家不要他想象的多麼高深額,其實說白了他就是一塊記憶體,它裡面存放的是我們的字串的引用。
大家可能疑問這個東西有什麼意義:假如我們要建立一個字串,"a" + "b" + "c" + ....+ 我們+了一萬次,那麼按照上面的說法,我們是不是為了建立一個很長的字串,建立了很多的物件。這樣不但有的字串被重複的建立了,而且佔用了很多不必要的記憶體,代價有點大。
我們的JVM為了減少字串的重複建立,維護了一個特殊的記憶體:就是這個字串常量池。
在這個字串常量池中,存放著我們字串物件的引用。下面我們根據字串建立物件的兩種形式來說明一下常量池是如何存放String物件引用的:
1.通過字面量建立String物件:
我們舉個例子,String str = "abc";我們通過字面量形式建立一個值為"abc"的物件,首先我們判斷常量池中是否存在一個引用,這個引用的值也是"abc",如果有這個值,那麼我們就從常量池中拿到這個引用,返回給str。
如果沒有,那麼我們就建立這個物件,然後把str這個引用值放到常量池中。
在這裡我們要明確幾個點了:
1首先常量池存放的不是字串物件,而是字串物件的引用。
2.無論常量池中是否存在這個物件的引用,最後結果都會存放有這個引用。
3.該方法可能不會建立新的物件。
2.通過new建立String物件。
這裡其實只有一句話,無論常量池中是否存在相同值的引用,至少建立一個物件。因為不管別的,new這個語法就一定會建立一個新的物件,然後我們要看構造方法中的字串,如果常量池中存在與該字串相同值的引用,那麼就不會建立新的物件,反之會建立物件之後,在常量池中存放這個引用。
這樣看起來我們的常量池的意義就很明確了,減少重複建立String物件,從而減少不必要的記憶體消耗。
如果要說他的弊端的話,應該就是犧牲了CPU的計算時間來換取空間吧,因為查詢是否有相同內容的引用是CPU耗時計算,但是與前者佔用記憶體相比,這個還是算不了什麼的吧(筆者自己觀點,沒有官方支援哈哈)。
intern()方法使用
最後我們來看一下這個intern()方法,這個方法其實理解為檢視常量池中是否存在對應內容的引用。如果有他會返回常量池中的引用,如果沒有則會將當前String的引用放入常量池。
我們舉幾個例子對比下就好:
public static void main(String[] args) {
String str1 = "gfzy";
String str2 = str1.intern();
System.out.println(str1 == str2);
}
他的結果是true。很簡單,我們首先通過字面量形式建立了一個“gfzy”物件,此時常量池中沒有相同內容引用,所以常量池存放str1的引用。
然後str2為str1的.intern()方法,現在常量池中存在“gfzy”這個內容的引用,所以我們返回這個引用,也就是str1,所以str1和str2指向同一個引用。
public static void main(String[] args) {
String str1 = new String("gfzy");
String str2 = "gfzy";
System.out.println(str1 == str2);
}
這個結果為false,首先我們看到str1是new了一個物件,所以他肯定是在堆記憶體中一塊新的記憶體,而構造方法中的“gfzy”,首先在常量池中會拿到他的引用,然後返回這個引用。
str2直接拿到了常量池中的引用,所以一個是堆記憶體新建立的,一個是原來常量池中的引用,兩者指向不是一個記憶體,所以是false。
而如果是這樣呢:
public static void main(String[] args) {
String str1 = new String("gfzy").intern();
String str2 = "gfzy";
System.out.println(str1 == str2);
}
這個結果為true。在原來的基礎上只是添加了一個inter方法,在new String("gfzy");時候,現在常量池中已經有了這個引用。而現在intern,我們會拿到常量池中的引用,所以str1為常量池中的引用,str2和上面一樣,所以返回true。
現在這幾個問題都解決完了,我們再返回頭看之前的兩個問題,第一個問題就很簡單了,而我們剛才也說過了。
第二個是看常量池中是否已經存在了對應的引用,如果沒有,那麼是"a","b","ab","c","abc","d","abcd"七個物件,如果常量池中存在引用,那麼只建立了一個“abcd”(這個是編譯器優化後的結果,其實筆者這裡也是有點懵逼,個人感覺應該是3個額,希望這個問題有讀者多多留言,幫我解答一下這個問題)。