Constant Pool常量池
的概念:
在講到String的一些特殊情況時,總會提到String Pool或者Constant Pool,但是我想很多人都不太
明白Constant Pool到底是個怎麼樣的東西,運行的時候存儲在哪裏,所以在這裏先說一下Constant Pool的內容.
String Pool是對應於在Constant Pool中存儲String常量的區域.習慣稱爲String Pool,也有人稱爲
String Constant Pool.好像沒有正式的命名??
在java編譯好的class文件中,有個區域稱爲Constant Pool,他是一個由數組組成的表,類型
爲cp_info constant_pool[],用來存儲程序中使用的各種常量,包括Class
/
String
/
Integer
等各
種基本Java數據類型,詳情參見The Java Virtual Machine Specification
4
.4章節.
關於String類的說明
1
.String使用private
final
char
value[]來實現字符串的存儲,也就是說String對象創建之後,就不能
再修改此對象中存儲的字符串內容,就是因爲如此,才說String類型是不可變的(immutable).
2
.String類有一個特殊的創建方法,就是使用
""
雙引號來創建
.例如new String(
"
i am
"
)實際創建了2個
String對象,一個是
"
i am
"
通過
""
雙引號創建的,另一個是通過new創建的.只不過他們創建的時期不同,
一個是編譯期,一個是運行期
!
3
.java對String類型重載了
+
操作符,可以直接使用
+
對兩個字符串進行連接.
4
.運行期調用String類的intern()方法可以向String Pool中動態添加對象.
String的創建方法一般有如下幾種
1
.直接使用
""
引號創建.
2
.使用new String()創建.
3
.使用new String(
"
someString
"
)創建以及其他的一些重載構造函數創建.
4
.使用重載的字符串連接操作符
+
創建.
例1
/*
* "sss111"是編譯期常量,編譯時已經能確定它的值,在編譯
* 好的class文件中它已經在String Pool中了,此語句會在
* String Pool中查找等於"sss111"的字符串(用equals(Object)方法確定),
* 如果存在就把引用返回,付值給s1.不存在就會創建一個"sss111"放在
* String Pool中,然後把引用返回,付值給s1.
*
*/
String s1
=
"
sss111
"
;
//
此語句同上
String s2
=
"
sss111
"
;
/*
* 由於String Pool只會維護一個值相同的String對象
* 上面2句得到的引用是String Pool中同一個對象,所以
* 他們引用相等
*/
System.out.println(s1
==
s2);
//
結果爲true
例2
/*
* 在java中,使用new關鍵字會創建一個新對象,在本例中,不管在
* String Pool中是否已經有值相同的對象,都會創建了一個新的
* String對象存儲在heap中,然後把引用返回賦給s1.
* 本例中使用了String的public String(String original)構造函數.
*/
String s1
=
new
String(
"
sss111
"
);
/*
* 此句會按照例1中所述在String Pool中查找
*/
String s2
=
"
sss111
"
;
/*
* 由於s1是new出的新對象,存儲在heap中,s2指向的對象
* 存儲在String Pool中,他們肯定不是同一個對象,只是
* 存儲的字符串值相同,所以返回false.
*/
System.out.println(s1
==
s2);
//
結果爲false
例3
String s1
=
new
String(
"
sss111
"
);
#008000">/*
* 當調用intern方法時,如果String Pool中已經包含一個等於此String對象
* 的字符串(用 equals(Object)方法確定),則返回池中的字符串.否則,將此
* String對象添加到池中,並返回此String對象在String Pool中的引用.
*/
s1
=
s1.intern();
String s2
=
"
sss111
"
;
/*
* 由於執行了s1 = s1.intern(),會使s1指向String Pool中值爲"sss111"
* 的字符串對象,s2也指向了同樣的對象,所以結果爲true
*/
System.out.println(s1
==
s2);
例4
String s1
=
new
String(
"
111
"
);
String s2
=
"
sss111
"
;
/*
* 由於進行連接的2個字符串都是常量,編譯期就能確定連接後的值了,
* 編譯器會進行優化
直接把他們表示成"sss111"存儲到String Pool中,
* 由於上邊的s2="sss111"已經在String Pool中加入了"sss111",
* 此句會把s3指向和s2相同的對象,所以他們引用相同.此時"sss"和"111"
* 兩個常量不會再創建.
*/
String s3
=
"
sss
"
+
"
111
"
;
/*
* 由於s1是個變量,在編譯期不能確定它的值是多少
,所以
* 會在執行的時候創建一個新的String對象存儲到heap中,
* 然後賦值給s4.
*/
String s4
=
"
sss
"
+
s1;
System.out.println(s2
==
s3);
//
true
System.out.println(s2
==
s4);
//
false
System.out.println(s2
==
s4.intern());
//
true
例5
這個是The Java Language Specification中3.
10
.5節的例子,有了上面的說明,這個應該不難理解了
package
testPackage;
class
Test {
public
static
void
main(String[] args) {
String hello
=
"
Hello
"
, lo
=
"
lo
"
;
System.out.print((hello
==
"
Hello
"
)
+
"
"
);
System.out.print((Other.hello
==
hello)
+
"
"
);
System.out.print((other.Other.hello
==
hello)
+
"
"
);
System.out.print((hello
==
(
"
Hel
"
+
"
lo
"
))
+
"
"
style="COLOR: #000000">);
System.out.print((hello
==
(
"
Hel
"
+
lo))
+
"
"
); //lo在runtime會創建一個新的對象
System.out.println(hello
==
(
"
Hel
"
+
lo).intern());
}
}
class
Other {
static
String hello
=
"
Hello
"
; }
package
other;
public
class
Other {
static
String hello
=
"
Hello
"
; }
輸出結果爲true
true
true
true
false
true
,請自行分析
!
結果上面分析,總結如下:
1
.單獨使用
""
引號創建的字符串都是常量,編譯期
就已經確定存儲到String Pool
中.
2
.使用new String(
""
)創建的對象會存儲到heap中,是運行期
新創建的.
3
.使用只包含常量的字符串連接符如
"
aa
"
+
"
aa
"
創
建的也是常量,編譯期就能確定,已經確定存儲到String Pool中.(編譯時會直接優化成"aaaa",如果String Pool
中沒有"aaaa",就用""號創建一個String,直接放到Pool中,比如:String t = "a"+ "b" +"c";
會優化成"abc",然後放入Pool中;又比如String s = "x"+"y"+ref;在編譯時有部分的優化:"xy",而ref +
"x" +"y"就不會有部分的優化,"+"從左到右執行,ref是變量,編譯時期無法確定)
4 .使用包含變量的字符串連接符如 " aa " + s1創建的對象是運行期才創建的,存儲在heap中.
(根據java api文檔中String類所講,會在內部使用stringbuffer及其append方法來實現連接,然後執行toString(),這樣就會在運行時又創建一個新對象)
還有幾個經常考的面試題:
1
.
String s1
=
new
String(
"
s1
"
) ;
String s2
=
new
String(
"
s1
"
) ;
上面創建了幾個String對象
?
答案:3個 ,編譯期Constant Pool中創建1個,運行期heap中創建2個.