一文弄懂String常量池,String常見面試題,以及intern()方法

String做為Java開發中常用的類,弄懂它是非常有必要的,但是往往很多工作了幾年從業人員,也並沒有特別熟悉過,所以樓主總結一下String的常量池,以及intern()方法等。技術無止境,當然本文也有不足之處,歡迎大家在評論區指正。

前言

本次程式碼使用 jdk 1。8版本,並且以下程式碼示例除了第一個寫了main()方法,並且所有的示例分別獨立執行 ,其餘為了簡潔做了預設main()。在建立字串分析的同時,都預設省略了棧中的控制代碼指向分析。進入正題之時,先科普幾個知識點

String原始碼裡面標註為final修飾的類,是一個不可改變的物件,那平時用到字串A+字串B怎麼改變了呢,其實這裡有涉及到String的常量池,首先常量池存放在方法區。

在jdk1。6時,方法區是存放在永久代(java堆的一部分,例如新生代,老年代)而在jdk1。7以後將字串常量池移動到了的堆記憶體中

在jdk1。8時,HotspotVM正式宣告了移除永久代,取而代之的是元資料區,元資料區存放在記憶體裡面(存放一些載入class的資訊),但是常量池還是和jdk1。7存放位置一樣還是存放在堆中。

先看一波常見面試題:

首先看一道常見的面試題,問輸出的是什麼?

public static void main(String[] args){

String s1 = new String(“123”);

String s2 = “123”;

System。out。println(s1 == s2);

}

基本上大家都能知道是false,但是再這麼深究一次,問 String s1 = new String(“123”) 建立了幾個物件,String s2 = “123” 建立 了幾個物件,那如果題目稍微改變一下成下面這樣,那輸出的又是什麼?

String s1 = new String(“123”)。intern();

String s2 = “123”;

System。out。println(s1 == s2); // true

// 如果這樣再改一下

String s1 = new String(“123”);

s1。intern();

String s2 = “123”;

System。out。println(s1 == s2); // false

如果對輸出結果不是很明白的,本文都會一一解答並且進行拓展。

一文弄懂String常量池,String常見面試題,以及intern()方法

建立字串分析:

首先要分析String,一定要知道String幾種常見的建立字串的方式,以及每一種不同的方式常量池和堆分別是什麼儲存情況。

1.直接寫雙引號常量來建立

判斷這個常量是否存在於常量池,

如果存在,則直接返回地址值(只不過地址值分為兩種情況,1是堆中的引用,2是本身常量池的地址)

如果是引用,返回引用地址指向的堆空間物件地址值

如果是常量,則直接返回常量池常量的地址值,

如果不存在,

在常量池中建立該常量,並返回此常量的地址值

String s = “123”;

//true,因為s已經在常量池裡面了,s。intern()返回的也是常量池的地址,兩者地址一樣為true

System。out。println(s == s。intern());

2. new String建立字串

與上面第一種方式相比,第一種方式效率高,下圖解決了本文中的最開始出的部分面試題。

首先在堆上建立物件(無論堆上是否存在相同字面量的物件),

然後判斷常量池上是否存在字串的字面量,

如果不存在

在常量池上建立常量(並將常量地址值返回)

如果存在

不做任何操作

String s = new String(“123”);

/*

嚴格來說首先肯定會在堆中建立一個123的物件,然後再去判斷常量池中是否存在123的物件,

如果不存在,則在常量池中建立一個123的常量(與堆中的123不是一個物件),

如果存在,則不做任何操作,解決了本文第一個面試題有問到建立幾個物件的問題。

因為常量池中是有123的物件的,s指向的是堆記憶體中的地址值,s。intern()返回是常量池中的123的常量池地址,所以輸出false

*/

System。out。println(s == s。intern());

3.兩個雙引號的字串相加

判斷這兩個常量、相加後的常量在常量池上是否存在

如果不存在

則在常量池上建立相應的常量(並將常量地址值返回)

如果存在,則直接返回地址值(只不過地址值分為兩種情況,1是堆中的引用,2是本身常量池的地址)

如果是引用,返回引用地址指向的堆空間物件地址值,

如果是常量,則直接返回常量池常量的地址值,

String s1 = new String(“123”)。intern();

String s2 = “1”+“23”;

/*

* 首先第一句話 String s1 = new String(“123”) 以上分析過建立了兩個物件(一個堆中,一個常量池 中)此時s1指向堆中

* 當s1呼叫。intern()方法之後,發現常量池中已經有了字面量是123的常量,則直接把常量池的地址返回給s1

* 在執行s2等於123時候,去常量池檢視,同上常量池已經存在了,則此時s2不建立物件,直接拿常量池123的地址值使用

* 所以此時s1 和 s2 都代表是常量池的地址值,則輸出為true

*/

System。out。println(s1 == s2);

如果這裡看不懂 intern()方法時,可以快速滑動到文章尾部,先看intern()方法的分析。

4.兩個new String()的字串相加

首先會建立這兩個物件(堆中)以及相加後的物件(堆中)

然後判斷常量池中是否存在這兩個物件的字面量常量

如果存在

不做任何操作

如果不存在

則在常量池上建立對應常量

String s1 = new String(“1”)+new String(“23”);

/*

* 首先堆中會有 1 ,23 ,以及相加之後的123 這三個物件。如果 1,23 這兩個物件在常量池中沒有相等的字面量

* 那麼還會在常量池中建立2個物件 最大建立了5個物件。最小建立了3個物件都在堆中。

*/

s1。intern();

String s2 = “123”;

System。out。println( s1 == s2);// true

這個地方比較複雜 ,如果我把String s2 = “123” 程式碼放在s1。intern()前面先執行,其餘程式碼不變,那麼輸出結果又為false,這裡等會樓主會在分析 intern()方法的時候再重點分析一次。

String s2 = “123”;

s1。intern();

System。out。println( s1 == s2);// false

5.雙引號字串常量與new String字串相加

首先建立兩個物件,一個是new String的物件(堆中),一個是相加後的物件(堆中)

然後判斷雙引號字串字面量和new String的字面量在常量池是否存在

如果存在

不做操作

如果不存在

則在常量池上建立物件的常量

String s1 = “1”+new String(“23”);

/*

*首先堆中會有 23 ,以及相加之後的123 這2個物件。如果23,1 這兩個物件在常量池中沒有相等的字面量

*那麼還會在常量池中建立2個物件最大建立了4個物件(2個堆中,2個在常量池中)。最小建立了2個物件都堆中。

*/

String s2 = “123”;

System。out。println( s1。intern() == s2);// true

6.雙引號字串常量與一個字串變數相加

首先建立一個物件,是相加後的結果物件(存放堆中,不會找常量池)

然後判斷雙引號字串字面量在常量池是否存在

如果存在

不做操作

如果不存在

則在常量池上建立物件的常量

String s1 = “23”;

/*

* 這裡執行時,常量“1” 會首先到字串常量池裡面去找,如果沒有就建立一個,並且加入字串常量池。

* 得到的123結果物件,不會存入到常量池。這裡特別注意和兩個常量字串相加不同 “1”+“23” 參考上面第三點

* 由於不會進入常量池,所以s2 和 s3 常量池地址值不同,所以輸出為false

*/

String s2 = “1”+s1;

String s3 = “123”;

System。out。println( s2 == s3。intern());

Q: 有人會問為什麼兩個常量字串相加得到的物件就會入常量池(參考上面第3點),而加上一個變數就不會???

A: 這是由於Jvm最佳化機制決定的,Jvm會有編譯時的最佳化,如果是兩個常量,Jvm會認定這已經是不可變的,就會直接在編譯 時和常量池進行判斷比對等,但是如果是加上一個變數,說明最後執行得出的結果是可變的,Jvm無法在編譯時就確定執 行之後的結果是多少,所以不會把該結果和常量池比對。

String。intern()方法分析:

在分析intern()方法時候,首先去官網檢視api的相關解釋

一文弄懂String常量池,String常見面試題,以及intern()方法

樓主大概翻譯一下,意思就是:當呼叫這個方法時候,如果常量池包含了一個相等的常量,就把該 常量池的物件返回,否則,就把當前物件加入到常量池中並且返回當前物件的引用。樓主用更加白話的方式解釋一下:

判斷這個常量是否存在於常量池。

如果存在,則直接返回地址值(只不過地址值分為兩種情況,1是堆中的引用,2是本身常量池的地址)

如果是引用,返回引用地址指向的堆空間物件地址值

如果是常量,則直接返回常量池常量的地址值,

如果不存在,

將當前物件引用複製到常量池,並且返回的是當前物件的引用(這個和上面最開始的字串建立分析有點不同)

實戰分析問題:

基本上讀者看到這裡就可以嘗試著去回過頭文章一些示例程式碼,看看輸出結果,這裡分析一下上文存在的一個例子

public static void main(String[] args){

String s1 = new String(“1”)+new String(“23”);

s1。intern();

String s2 = “123”;

System。out。println( s1 == s2);

}

分析: 1 首先看第一行是兩個new String型別的字串相加(詳見上文第4點)可知道,這裡建立了堆中有3個物件 一個是1, 一個是23,還有一個是結果 123,由於程式剛啟動常量池也沒有 1,23 所以會在常量池建立2個物件 (1 , 23)

2 當s1執行intern()方法之後,首先去常量池判斷有沒有123,此時發現沒有,所以會把物件加入到常量池,並且返回 當前物件的引用(堆中的地址)

3 當建立s2時候(詳見上文第1點),並且找到常量池中123,並且把常量池的地址值返回給s2

4 由於常量池的地址值就是s1呼叫intern()方法之後得到的堆中的引用,所以此時s1和s2的地址值一樣,輸出true。

public static void main(String[] args){

String s1 = new String(“1”)+new String(“23”);

String s2 = “123”;

s1。intern();

System。out。println( s1 == s2);

}

如果把中間兩行換一個位置,那輸出就是false了,下面在分析一下不同點,上面分析過的不再贅述。

1。在執行到第二行的時候String s2 = “123”時,發現常量池沒有123,所以會先建立一個常量

2。在當s1呼叫intern()方法時,會發現常量池已經有了123物件,就會直接把123的常量給返回出去,但是由於返回值並沒有接 收,所以此時s1還是堆中地址,則輸入false;如果程式碼換成 s1 = s1。intern();那s1就會重新指向常量池了,那輸出就為true;

結尾:

由於本文都是在Jdk1。8版本(1。7由於已經把常量池放在堆中了和1。8結果應該一樣)執行,如果有讀者要探究1。6的相關問題,主要知道jdk1。6在建立String和1。8(1。7)的堆和常量池有一些不同實現,那相關問題就很清楚了,這裡樓主沒有涉及到1。6的相關問題,以免混淆讀者,如果需要探究1。6以及相關問題,歡迎在評論區留言。

樓主很多時候都說了控制代碼指標的概念相對抽象,如果讀者想知道兩個物件是否是指向了相同的地址,可用 System。identityHashCode(Object x) 來驗證。

最後本文也肯定有一些不足之處,歡迎大家在評論區留言,學無止境,大家加油。

一文弄懂String常量池,String常見面試題,以及intern()方法

原文連結:https://blog。csdn。net/xzjayx/article/details/103020029