java運(yùn)行環(huán)境有一個(gè)字符串池(string pool),由String類維護(hù)。
執(zhí)行語(yǔ)句 String str = "abc" 時(shí),首先查看字符串池中是否存在字符串"abc",如果存在則直接將"abc"地址賦給str ,如果不存在則先在字符串池中新建一個(gè)字符串"abc",然后再將其賦給str。
執(zhí)行語(yǔ)句 String str = new String("abc") 時(shí),不管字符串池中是否存在"abc",直接新建一個(gè)字符串"abc"(注意:新建的字符串"abc" 不是在字符串池中),然后將其賦給str。
前一語(yǔ)句的效率高,后一語(yǔ)句的效率低,因?yàn)樾陆ㄗ址加脙?nèi)存空間。String str = newString();創(chuàng)建了一個(gè)空字符串,與String str = new String("");相同。
public String intern()
返回字符串對(duì)象的規(guī)范化表示形式。一個(gè)初始為空的字符串池,它由類String私有地維護(hù)。當(dāng)調(diào)用intern()方法時(shí),如果字符串池中已經(jīng)包含一個(gè)等于此String對(duì)象的字符串 (用equals(Object)方法確定),則返回字符串池中的字符串。否則,將此String對(duì)象添加到字符串池中,并返回此String 對(duì)象的引用。它遵循以下規(guī)則:對(duì)于任意兩個(gè)字符串s 和 t,當(dāng)且僅當(dāng)s.equals(t)為true時(shí),s.intern() == t.intern() 才為true。
String.intern();
再補(bǔ)充一點(diǎn):存在于.class 文件中的常量池,在運(yùn)行期間被jvm(java virtualmachine)裝載,并且可以擴(kuò)充。String 的 intern()方法就是擴(kuò)充常量池的一個(gè)方法;當(dāng)一個(gè)String實(shí)例(instance)str調(diào)用intern()方法時(shí),java 查找常量池中是否有相同 unicode的字符串常量,如果有,則返回其引用,如果沒有,則在常量池中增加一個(gè)unicode 等于str 的字符串并返回其引用。
簡(jiǎn)單例子:
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);
輸出結(jié)果為:
False
False
True
True
最后再破除一個(gè)錯(cuò)誤的理解:
有人說,"使用String.intern()方法可以將一個(gè)String類保存到一個(gè)全局的String表中,如果具有相同值的unicode字符串已經(jīng)在這個(gè)表中,那么該方法返回表中已有字符串的地址,如果在表中沒有相同unicode的字符串,則將自己的地址注冊(cè)到表中",如果我們把這個(gè)全局的String表理解成常量池的話,最后一句話"如果在表中沒有相同值的字符串,則將自己的地址注冊(cè)到表中"是錯(cuò)的。
簡(jiǎn)單例子:
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());
輸出結(jié)果是:
False
kvill kvill
True
我們沒有聲明一個(gè)"kvill"常量,所以常量池中一開始沒有"kvill"的,當(dāng)我們調(diào)用s1.intern()后,就在常量池中新添加了一個(gè)"kvill"常量,原來的不在常量池中的"kvill"仍然存在,也就不是"把自己的地址注冊(cè)到常量池中"了。
例子1):
String str1 = "java"; // str1指向字符串池
String str2 = "blog"; // str2指向字符串池
String s = str1 + str2; // s是指向堆中值為"javablog"的對(duì)象,+運(yùn)算符會(huì)在堆中建立起來兩個(gè)String對(duì)象,這兩個(gè)對(duì)象分別是"java","blog",也就是說從字符串池中復(fù)制這兩個(gè)值,然后在堆中創(chuàng)建兩個(gè)對(duì)象,然后再建立對(duì)象s,然后將"javablog"的堆地址賦給s。// 這條語(yǔ)句總共創(chuàng)建了多少個(gè)對(duì)象?
System.out.println(s == "javablog"); // 結(jié)果為False
jvm(java virtual machine)確實(shí)對(duì)形如String str1 = "java";的String對(duì)象放在常量池中,但是它是在編譯時(shí)那么做的,而String s = str1 + str2;是在運(yùn)行時(shí)才知道的,也就是說str1 + str2 是在堆里創(chuàng)建的,所以結(jié)果為false了。
如果改成以下兩種方式:
String s = "java" + "blog";
System.out.println(s == "javablog");
String s = str1 + "blog"; // 不放入字符串池,而是在堆中分配
System.out.println(s == "javablog"); // 結(jié)果為false
引用變量與對(duì)象的區(qū)別:
字符串"abc" 是一個(gè)String 對(duì)象,字符串池(pool of literalstrings)和堆(heap)中存放著字符串對(duì)象
一、引用變量與對(duì)象
A a;
這個(gè)語(yǔ)句聲明了一個(gè)類A的引用變量a,而對(duì)象一般通過new關(guān)鍵字創(chuàng)建,所以a僅僅是一個(gè)引用變量,而不是對(duì)象
二、java中所有的字符串文字(字符串常量)都是一個(gè)String類的對(duì)象。有人(特別是c程序員)在一些場(chǎng)合喜歡把字符串"當(dāng)做/看成"字符數(shù)組,這也沒有辦法,因?yàn)樽址c字符數(shù)組存在一些內(nèi)在的聯(lián)系。事實(shí)上,它與字符數(shù)組是兩種完全不同的對(duì)象。
三、字符串對(duì)象的創(chuàng)建
由于字符串對(duì)象的大量使用(它是一個(gè)對(duì)象,一般而言對(duì)象總是在堆(heap)中分配內(nèi)存),java中為了節(jié)省內(nèi)存空間和運(yùn)行時(shí)間(如比較字符串時(shí),==比 equals()方法快),在編譯階段就把所有的字符串文字放到一個(gè)字符串池(pool of literalstrings)中,而運(yùn)行時(shí)字符串池成為常量池的一部分。字符串池的好處,就是該池中所有相同的字符串常量被合并,只占用一個(gè)空間。
我們知道,對(duì)兩個(gè)引用變量,使用 == 判斷它們的值(引用)是否相等,即是否指向同一個(gè)對(duì)象:
String s1 = "abc";
String s2 = "abc";
if(s1 == s2) System.out.println("s1,s2 refer to the sameobject");
else
這里的輸出顯示,兩個(gè)字符串文字保存為一個(gè)對(duì)象,就是說,上面的代碼只在字符串池(string pool)中創(chuàng)建了一個(gè)對(duì)象。
現(xiàn)在看看String s = newString("abc");語(yǔ)句,這里"abc"本身就是pool中的一個(gè)對(duì)象,而在運(yùn)行時(shí)執(zhí)行newString()時(shí),將pool中的對(duì)象復(fù)制一份放到heap中,并且把heap中的這個(gè)對(duì)象的引用交給s持有。ok,這條語(yǔ)句就創(chuàng)建了2個(gè)String對(duì)象。
String s1 = new String("abc");
String s2 = new String("abc");
if(s1 == s2) { } // 不會(huì)執(zhí)行的語(yǔ)句
這里用 ==判斷就可知,雖然兩個(gè)對(duì)象的"內(nèi)容"相同(equals()判斷),但兩個(gè)引用變量所持有的引用不同,上面的代碼創(chuàng)建了幾個(gè)StringObject? (三個(gè),pool中一個(gè),heap中兩個(gè))
綜上所述:
創(chuàng)建字符串有兩種方式:兩種內(nèi)存區(qū)域(pool vs heap)
1. "" 引號(hào)創(chuàng)建的字符串在字符串池中
2. new, new關(guān)鍵字創(chuàng)建字符串時(shí)首先查看字符串池(stringpool)中是否有相同值的字符串,如果有,則拷貝一份到堆(heap)中,然后返回堆中的地址;如果字符串池中沒有,則在堆中創(chuàng)建一份,然后返回堆(heap)中的地址(注意,此時(shí)不需要從堆中復(fù)制到池中,否則,將使得堆中的字符串永遠(yuǎn)是池中的子集,導(dǎo)致浪費(fèi)字符串池的內(nèi)存空間)
3. 對(duì)字符串進(jìn)行賦值時(shí),如果右操作數(shù)含有一個(gè)或一個(gè)以上的字符串引用時(shí),則在堆中再建立一個(gè)字符串對(duì)象,返回引用;如Strings = str1 + "blog";
比較兩個(gè)已經(jīng)存在于字符串池中字符串對(duì)象可以用 == 進(jìn)行,擁有比equals操作更快的速度。
聯(lián)系客服