天天看點

Java基礎String字元串常量池和intern方法,讓你面試加分的回答!

作者:千鋒教育

前言

在之前的文章中我們給大家介紹了String字元串的不可變性及其實作原理,其中給大家提到了字元串常量池的概念。 那麼什麼是常量池?String字元串與常量池有什麼關系?常量池中存儲的内容有什麼特點?要想搞清楚這些問題,咱們再再利用一篇文章給大家唠唠字元串常量池及String#intern()方法的作用。

全文大約 【2300】字, 不說廢話,隻講可以讓你學到技術、明白原理的純幹貨!本文帶有豐富的案例及配圖,讓你更好地了解和運用文中的技術概念,并可以給你帶來具有足夠啟迪的思考

一. 常量池簡介

1. 基本概念

常量池是堆中的一塊存儲區域,用于存儲顯式的String、float、Integer等資料。這是一個特殊的共享區域,開發時不需要在記憶體中經常改變的資料,都可以放在這裡進行共享。JDK 7及其之前的常量池是在方法區中,從Java8之後,常量池存放到了堆中。

為了讓大家更好地了解常量池的作用,給大家分析一下String字元串的記憶體配置設定

2. 實驗案例

我們先來編寫一行代碼,如下所示:

//String對象建立
String s = new String("xyz");           

這個代碼很簡單,就一行代碼!那麼問題來了,這行代碼中幾個對象的記憶體配置設定是如何的?接下來就給大家把這段代碼的記憶體分區繪制一下(本案例開發環境是基于JDK8)。

3. 記憶體配置設定(重點)

在 String s = new String("xyz"); 這行代碼中,s是String類型的變量,不是對象!‘xyz’是字元串對象,new String("xyz")也是一個對象,那麼它們幾個的記憶體劃分在JDK8的環境中,如下圖所示:

Java基礎String字元串常量池和intern方法,讓你面試加分的回答!

根據上圖,給大家分析一下上述代碼的記憶體配置設定情況,如下所示:

1. 當JVM在編譯階段加載讀取到“xyz”的時候,首先會檢查堆中的String常量池,也就是常量緩沖區。檢查是否已經有了"xyz"常量對象,如果有,則不會再次建立"xyz"常量對象,并直接傳回該字元串的引用位址;如果沒有,則建立一個"xyz"常量對象,并為該對象配置設定一個記憶體位址002傳回;

2. 當JVM在運作階段加載讀取到new關鍵字的時候,JVM會在堆中為其建立一個對象,即new String(),并為其配置設定記憶體位址001,而堆中這個對象的内容是上面"xyz"常量對象的引用位址002,換句話說這個堆中存的就是常量池中"xyz"的引用位址002;

3. 最後,s 是對目前堆中001号對象的一個位址引用,s本身不是一個對象,s隻是一個String類型的變量而已!

二. intern()方法(重點)

了解了常量池的内容之後,接下來請大家再跟着我們來看看 String的intern()方法,這個方法很重要,請大家記住哦。

/**

* Returns a canonical representation for the string object.

* <p>

* A pool of strings, initially empty, is maintained privately by the

* class {@code String}.

* <p>

* When the intern method is invoked, if the pool already contains a

* string equal to this {@code String} object as determined by

* the {@link #equals(Object)} method, then the string from the pool is

* returned. Otherwise, this {@code String} object is added to the

* pool and a reference to this {@code String} object is returned.

* <p>

* It follows that for any two strings {@code s} and {@code t},

* {@code s.intern() == t.intern()} is {@code true}

* if and only if {@code s.equals(t)} is {@code true}.

* <p>

* All literal strings and string-valued constant expressions are

* interned. String literals are defined in section 3.10.5 of the

* <cite>The Java™ Language Specification</cite>.

*

* @return a string that has the same contents as this string, but is

* guaranteed to be from a pool of unique strings.

*/

public native String intern();           

從上面的源碼注釋中我們可以知道,intern()是由C語言實作的native底層方法,用于從String緩存池中擷取一個與該字元串内容相同的字元串對象。當這個intern()方法被執行的時候,如果緩存池中已經有這個String内容,則直接從這個緩存池中擷取該String内容對象;如果緩存池中沒有這個String内容對象,則把這個String内容對象放到緩存池中,并傳回這個字元串對象的引用。 現在我們知道了intern()方法的功能,但是該方法的底層原理是什麼樣的呢?

接下來給結合一段代碼案例,給各位詳細說一下:

//常量池與堆的關系
String str1="yiyige";
String str2=new String("yiyige");
System.out.println("str1==str2的結果==> " +(str1==str2));

String str3 = str2.intern();
System.out.println("str1==str3的結果==> " +(str1==str3));           

執行結果如下圖所示:

Java基礎String字元串常量池和intern方法,讓你面試加分的回答!

intern()方法的底層原理如下(重點):

Java專門為String類設計了一個緩存池intern pool,intern pool是在方法區中的一塊特殊存儲區域,當我們通過 String str="yiyige" 這樣的方式來構造一個新的字元串時,String類會優先在緩存池中查找是否已經存在内容相同的String對象。

如果有則直接傳回該對象的位址引用,如果沒有就會構造一個新的String對象,然後放進緩存池,再傳回該字元串的位址引用。是以,即使我們構造一萬個String str = "yiyige",但實際上得到的都是同一個位址引用,這樣就避免了很多不必要的空間開銷。注意:intern池不适用new String("yiyige")的構造形式!

注意:

因為字元串常量池存放位置發生了變化,String類對intern()方法也進行了一些修改:

JDK 6 版本中執行intern()方法時,首先會判斷字元串常量池中是否存在該字元串字面量,如果不存在則拷貝一份字元串字面量存放到常量池中,最後傳回該字元串字面量的唯一引用。如果發現字元串常量池中已經存在,則直接傳回該字元串字面量的唯一引用。

JDK 7 以後執行intern()方法時,如果發現字元串常量池中不存在該字元串字面量,則不會再拷貝一份字面量,而是拷貝字面量對應堆中的一個位址引用,然後傳回這個引用。

現在我們知道了,原來當一個String對象被建立時,如果發現目前String對象已經存在于String Pool中了,就會傳回一個已存在的String對象引用而不會建立一個對象。比如以下代碼隻會在常量池中建立一個String對象。

String str1 = "yiyige"; 
String str2 = "yiyige";           

建立過程如下圖所示:

Java基礎String字元串常量池和intern方法,讓你面試加分的回答!

如果一個String是可變的,當改變了A引用指向的String時,可能就會導緻其他的B引用得到錯誤的值,是以Sting就被設計為不可變的。

String底層主要是使用intern緩存池将字元串緩存起來,同時允許把一個String字元串的位址指派給多個String變量來引用,這樣就可以保證多個變量安全地共享同一個對象。 如果Java中的String對象可變的話,一個字元串引用的操作改變了對象的值,那麼其他的變量就會受到影響。

三. 結語

至此,我們就把字元串相關的一些正常原理性知識點,給大家講解梳理完畢了。

更多精彩内容,關注@千鋒教育

繼續閱讀