java字符串池(string pool)和字符串堆(heap)內存分配

java運行環境有一個字符串池(string pool),由String類維護。

 

執行語句 String str = "abc" 時,首先查看字符串池中是否存在字符串"abc",如果存在則直接將"abc"地址賦給str ,如果不存在則先在字符串池中新建一個字符串"abc",然後再將其賦給str。

 

執行語句 String str = new String("abc") 時,不管字符串池中是否存在"abc",直接新建一個字符串"abc"(注意:新建的字符串"abc" 不是在字符串池中),然後將其賦給str。

 

前一語句的效率高,後一語句的效率低,因爲新建字符串佔用內存空間。String str = newString();創建了一個空字符串,與String str = new String("");相同。

 

public String intern()

返回字符串對象的規範化表示形式。一個初始爲空的字符串池,它由類String私有地維護。當調用intern()方法時,如果字符串池中已經包含一個等於此String對象的字符串 (用equals(Object)方法確定),則返回字符串池中的字符串。否則,將此String對象添加到字符串池中,並返回此String 對象的引用。它遵循以下規則:對於任意兩個字符串s 和 t,當且僅當s.equals(t)爲true時,s.intern() == t.intern() 才爲true。

 

String.intern();

再補充一點:存在於.class 文件中的常量池,在運行期間被jvm(java virtualmachine)裝載,並且可以擴充。String 的 intern()方法就是擴充常量池的一個方法;當一個String實例(instance)str調用intern()方法時,java 查找常量池中是否有相同 unicode的字符串常量,如果有,則返回其引用,如果沒有,則在常量池中增加一個unicode 等於str 的字符串並返回其引用。

 

簡單例子:

String s = "kvill";

String s1 = new String("kvill");

String s2 = new String("kvill");

System.out.println(s == s1);

s1.intern();

s2 = s2.intern();

System.out.println(s == s1);

System.out.println(s == s1.intern());

System.out.println(s == s2);

輸出結果爲:

False

False  //雖然執行了s1.intern(),但是它的返回值並沒有賦給s1

True

True

 

最後再破除一個錯誤的理解:

有人說,"使用String.intern()方法可以將一個String類保存到一個全局的String表中,如果具有相同值的unicode字符串已經在這個表中,那麼該方法返回表中已有字符串的地址,如果在表中沒有相同unicode的字符串,則將自己的地址註冊到表中",如果我們把這個全局的String表理解成常量池的話,最後一句話"如果在表中沒有相同值的字符串,則將自己的地址註冊到表中"是錯的。

簡單例子:

String s1 = new String("kvill");

String s2 = s1.intern();

System.out.println(s1 == s1.intern());

System.out.println(s1 + " " + s2);

System.out.println(s2 == s1.intern());

輸出結果是:

False

kvill kvill

True

我們沒有聲明一個"kvill"常量,所以常量池中一開始沒有"kvill"的,當我們調用s1.intern()後,就在常量池中新添加了一個"kvill"常量,原來的不在常量池中的"kvill"仍然存在,也就不是"把自己的地址註冊到常量池中"了。

 

 

例子1):

String str1 = "java"; // str1指向字符串池

String str2 = "blog"; // str2指向字符串池

 

String s = str1 + str2; // s是指向堆中值爲"javablog"的對象,+運算符會在堆中建立起來兩個String對象,這兩個對象分別是"java","blog",也就是說從字符串池中複製這兩個值,然後在堆中創建兩個對象,然後再建立對象s,然後將"javablog"的堆地址賦給s。// 這條語句總共創建了多少個對象?

 

System.out.println(s == "javablog"); // 結果爲False

jvm(java virtual machine)確實對形如String str1 = "java";的String對象放在常量池中,但是它是在編譯時那麼做的,而String s = str1 + str2;是在運行時才知道的,也就是說str1 + str2 是在堆裏創建的,所以結果爲false了。

 

如果改成以下兩種方式:

String s = "java" + "blog";  //直接將"javablog"放入字符串池中

System.out.println(s == "javablog");  //結果爲true

String s = str1 + "blog"; // 不放入字符串池,而是在堆中分配

System.out.println(s == "javablog"); // 結果爲false

 

 

 

引用變量與對象的區別:

字符串"abc" 是一個String 對象,字符串池(pool of literalstrings)和堆(heap)中存放着字符串對象

 

一、引用變量與對象

A a;

這個語句聲明瞭一個類A的引用變量a,而對象一般通過new關鍵字創建,所以a僅僅是一個引用變量,而不是對象

 

二、java中所有的字符串文字(字符串常量)都是一個String類的對象。有人(特別是c程序員)在一些場合喜歡把字符串"當做/看成"字符數組,這也沒有辦法,因爲字符串與字符數組存在一些內在的聯繫。事實上,它與字符數組是兩種完全不同的對象。

 

三、字符串對象的創建

由於字符串對象的大量使用(它是一個對象,一般而言對象總是在堆(heap)中分配內存),java中爲了節省內存空間和運行時間(如比較字符串時,==比 equals()方法快),在編譯階段就把所有的字符串文字放到一個字符串池(pool of literalstrings)中,而運行時字符串池成爲常量池的一部分。字符串池的好處,就是該池中所有相同的字符串常量被合併,只佔用一個空間。

 

我們知道,對兩個引用變量,使用 == 判斷它們的值(引用)是否相等,即是否指向同一個對象:

String s1 = "abc";

String s2 = "abc";

if(s1 == s2) System.out.println("s1,s2 refer to the sameobject");

else System.out.println("trouble");

這裏的輸出顯示,兩個字符串文字保存爲一個對象,就是說,上面的代碼只在字符串池(string pool)中創建了一個對象。

 

現在看看String s = newString("abc");語句,這裏"abc"本身就是pool中的一個對象,而在運行時執行newString()時,將pool中的對象複製一份放到heap中,並且把heap中的這個對象的引用交給s持有。ok,這條語句就創建了2個String對象。

 

String s1 = new String("abc");

String s2 = new String("abc");

if(s1 == s2) { } // 不會執行的語句

這裏用 ==判斷就可知,雖然兩個對象的"內容"相同(equals()判斷),但兩個引用變量所持有的引用不同,上面的代碼創建了幾個StringObject? (三個,pool中一個,heap中兩個)

 

 

 

綜上所述:

創建字符串有兩種方式:兩種內存區域(pool vs heap)

1. "" 引號創建的字符串在字符串池中

2. new, new關鍵字創建字符串時首先查看字符串池(stringpool)中是否有相同值的字符串,如果有,則拷貝一份到堆(heap)中,然後返回堆中的地址;如果字符串池中沒有,則在堆中創建一份,然後返回堆(heap)中的地址(注意,此時不需要從堆中複製到池中,否則,將使得堆中的字符串永遠是池中的子集,導致浪費字符串池的內存空間)

3. 對字符串進行賦值時,如果右操作數含有一個或一個以上的字符串引用時,則在堆中再建立一個字符串對象,返回引用;如Strings = str1 + "blog";

比較兩個已經存在於字符串池中字符串對象可以用 == 進行,擁有比equals操作更快的速度。

發佈了18 篇原創文章 · 獲贊 13 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章