什么是字符串常量池?
在理解字符串常量前,我們先熟悉一下如何創建一個字符串,在Java中有兩種方法可以創建一個字符串對象:
- 使用new運算符。例如:
|
1
|
String str =
new
String(
"Hello"
);
|
- 使用字符串常量或者常量表達式。例如:
|
1
2
|
String str=
"Hello"
;
//(字符串常量) 或者
String str=
"Hel"
+
"lo"
;
//(字符串常量表達式).
|
這些字符串的創建方式之間有什么區別呢?在Java中,equals方法被認為是對象的值進行深層次的比較,而操作符==是進行的淺層次的比較。 equals方法比較兩個對象的內容而不是引用。==兩側是引用類型(例如對象)時,如果引用是相同的-即指向同一個對象-則執行結果為真。如果是值類型 (例如原生類型),如果值相同,則執行結果為真。equals方法在兩個對象具有相同內容時返回真-但是,java.lang.Object類中的 equals方法返回真-如果類沒有覆蓋默認的equals方法,如果兩個引用指向同一個對象。
讓我們通過下面的例子來看看這兩種字符串的創建方式之間有什么區別吧。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public
class
DemoStringCreation {
public
static
void
main(String args[]) {
String str1 =
"Hello"
;
String str2 =
"Hello"
;
System.out.println(
"str1 and str2 are created by using string literal."
);
System.out.println(
" str1 == str2 is "
+ (str1 == str2));
System.out.println(
" str1.equals(str2) is "
+ str1.equals(str2));
String str3 =
new
String(
"Hello"
);
String str4 =
new
String(
"Hello"
);
System.out.println(
"str3 and str4 are created by using new operator."
);
System.out.println(
" str3 == str4 is "
+ (str3 == str4));
System.out.println(
" str3.equals(str4) is "
+ str3.equals(str4));
String str5 =
"Hel"
+
"lo"
;
String str6 =
"He"
+
"llo"
;
System.out.println(
"str5 and str6 are created by using string constant expression."
);
System.out.println(
" str5 == str6 is "
+ (str5 == str6));
System.out.println(
" str5.equals(str6) is "
+ str5.equals(str6));
String s =
"lo"
;
String str7 = "Hel" + s;
String str8 = "He" + "llo";
System.out.println(
"str7 is computed at runtime."
);
System.out.println(
"str8 is created by using string constant expression."
);
System.out.println(
" str7 == str8 is "
+ (str7 == str8));
System.out.println(
" str7.equals(str8) is "
+ str7.equals(str8));
}
}
|
輸出結果為:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
str1 and str2 are created by using string literal.
str1 == str2 is
true
str1.equals(str2) is
true
str3 and str4 are created by using
new
operator.
str3 == str4 is
false
str3.equals(str4) is
true
str5 and str6 are created by using string constant expression.
str5 == str6 is
true
str5.equals(str6) is
true
str7 is computed at runtime.
str8 is created by using string constant expression.
str7 == str8 is false
str7.equals(str8) is true
|
使用相同的字符序列而不是使用new關鍵字創建的兩個字符串會創建指向Java字符串常量池中的同一個字符串的指針。字符串常量池是Java節約資源的一種方式。
字符串常量池
字符串的分配,和其他的對象分配一樣,耗費高昂的時間與空間代價。JVM為了提高性能和減少內存開銷,在實例化字符串常量的時候進行了一些優化。為 了減少在JVM中創建的字符串的數量,字符串類維護了一個字符串池,每當代碼創建字符串常量時,JVM會首先檢查字符串常量池。如果字符串已經存在池中, 就返回池中的實例引用。如果字符串不在池中,就會實例化一個字符串並放到池中。Java能夠進行這樣的優化是因為字符串是不可變的,可以不用擔心數據沖突 進行共享。例如:
|
1
2
3
4
5
6
7
8
9
|
public
class
Program
{
public
static
void
main(String[] args)
{
String str1 =
"Hello"
;
String str2 =
"Hello"
;
System.out.print(str1 == str2);
}
}
|
其結果是:
|
1
|
true
|
不幸的是,當使用:
|
1
|
String a=
new
String(
"Hello"
);
|
一個字符串對象在字符串常量池外創建,即使池里存在相同的字符串。考慮到這些,要避免new一個字符串除非你明確的知道需要這么做!例如:
|
1
2
3
4
5
6
7
8
9
10
|
public
class
Program
{
public
static
void
main(String[] args)
{
String str1 =
"Hello"
;
String str2 =
new
String(
"Hello"
);
System.out.print(str1 == str2 +
" "
);
System.out.print(str1.equals(str2));
}
}
|
結果是:
|
1
|
false
true
|
JVM中有一個常量池,任何字符串至多維護一個對象。字符串常量總是指向字符串池中的一個對象。通過new操作符創建的字符串對象不指向字符串池中 的任何對象,但是可以通過使用字符串的intern()方法來指向其中的某一個。java.lang.String.intern()返回一個保留池字符 串,就是一個在全局字符串池中有了一個入口。如果以前沒有在全局字符串池中,那么它就會被添加到里面。例如:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
Program
{
public
static
void
main(String[] args)
{
// Create three strings in three different ways.
String s1 =
"Hello"
;
String s2 =
new
StringBuffer(
"He"
).append(
"llo"
).toString();
String s3 = s2.intern();
// Determine which strings are equivalent using the ==
// operator
System.out.println(
"s1 == s2? "
+ (s1 == s2));
System.out.println(
"s1 == s3? "
+ (s1 == s3));
}
}
|
輸出是:
|
1
2
|
s1 == s2?
false
s1 == s3?
true
|
//intern() 返回字符串對象的規范化表示形式。也就是把對象轉化成字符串常量
為了優化空間,運行時實例創建的全局字符串常量池中有一個表,總是為池中每個唯一的字符串對象維護一個引用。這就意味着它們一直引用着字符串常量池中的對象,所以,在常量池中的這些字符串不會被垃圾收集器回收。
Java語言規范第三版中的字符串常量
每一個字符串常量都是指向一個字符串類實例的引用。字符串對象有一個固定值。字符串常量,或者一般的說,常量表達式中的字符串都被使用方法 String.intern進行保留來共享唯一的實例。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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"
)) +
" "
);
System.out.print((hello == (
"Hel"
+lo)) +
" "
);
System.out.println(hello == (
"Hel"
+lo).intern());
}
}
class
Other {
static
String hello =
"Hello"
; }
|
編譯單元:
|
1
2
|
package
other;
public
class
Other {
static
String hello =
"Hello"
; }
|
產生輸出:
|
1
|
true
true
true
true
false
true
|
這個例子說明了六點:
- 同一個包下同一個類中的字符串常量的引用指向同一個字符串對象;
- 同一個包下不同的類中的字符串常量的引用指向同一個字符串對象;
- 不同的包下不同的類中的字符串常量的引用仍然指向同一個字符串對象;
- 由常量表達式計算出的字符串在編譯時進行計算,然后被當作常量;
- 在運行時通過連接計算出的字符串是新創建的,因此是不同的;
- 通過計算生成的字符串顯示調用intern方法后產生的結果與原來存在的同樣內容的字符串常量是一樣的。
原文鏈接: xyzws 翻譯: ImportNew.com - lumeng689
譯文鏈接: http://www.importnew.com/10756.html
[ 轉載請保留原文出處、譯者和譯文鏈接。]
