Java專欄:字符串常量池、String詳解
關(guān)注▼Java學(xué)習(xí)之道▼一起成長,一起學(xué)習(xí)~
作者: 遠(yuǎn)赴山河萬里
來源: blog.csdn.net/weixin_43615816/article/details/122882249
Part1String Pool
String 的字符串常量池(String Pool)是一個(gè)固定大小的 HashTable(數(shù)組+鏈表的數(shù)據(jù)結(jié)構(gòu)),故不存在兩個(gè)相同的字符串。也叫 StringTable。StringTable是放在本地內(nèi)存的,是C++寫的,里面放的是字符串對(duì)象的引用,真實(shí)的字符串對(duì)象是在堆里。
1.1字符串常量池產(chǎn)生時(shí)間
像這些靜態(tài)的、未加載的.class文件的數(shù)據(jù)被稱為靜態(tài)常量池,但經(jīng)過jvm把.class文件裝入內(nèi)存、加載到方法區(qū)后,常量池就會(huì)變?yōu)檫\(yùn)行時(shí)常量池。
當(dāng)類加載到內(nèi)存中后,jvm就會(huì)將class常量池中的內(nèi)容存放到運(yùn)行時(shí)常量池中,運(yùn)行時(shí)常量池存在于內(nèi)存中,也就是class常量池被加載到內(nèi)存之后的版本。
不同之處是:它的字面量可以動(dòng)態(tài)的添加(String#intern()),符號(hào)引用可以被解析為直接引用。
簡單來說,HotSpot VM里StringTable是個(gè)哈希表,里面存的是駐留字符串的引用(而不是駐留字符串實(shí)例自身)。也就是說某些普通的字符串實(shí)例被這個(gè)StringTable引用之后就等同被賦予了“駐留字符串”的身份。這個(gè)StringTable在每個(gè)HotSpot VM的實(shí)例里只有一份,被所有的類共享。類的運(yùn)行時(shí)常量池里的CONSTANT_String類型的常量,經(jīng)過解析(resolve)之后,同樣存的是字符串的引用;解析的過程會(huì)去查詢StringTable,以保證運(yùn)行時(shí)常量池所引用的字符串與StringTable所引用的是一致的。
| 字符串常量池 |
|---|
| 本質(zhì)就是一個(gè)哈希表 |
| 存儲(chǔ)的是字符串實(shí)例的引用 |
| 在被整個(gè)JVM共享 |
| 在解析運(yùn)行時(shí)常量池中的符號(hào)引用時(shí),會(huì)去查詢字符串常量池,確保運(yùn)行時(shí)常量池中解析后的直接引用跟字符串常量池中的引用是一致的 |
1.2字符串常量池的位置
以下圖表示的是運(yùn)行時(shí)數(shù)據(jù)區(qū)


1.3字符串常量池的優(yōu)點(diǎn)
為了避免頻繁的創(chuàng)建和銷毀對(duì)象而影響系統(tǒng)性能,實(shí)現(xiàn)了對(duì)象的共享。 例如字符串常量池,在編譯階段就把所有的字符串文字放到一個(gè)常量池中。 節(jié)省內(nèi)存空間:常量池中所有相同的字符串常量被合并,只占用一個(gè)空間。 節(jié)省運(yùn)行時(shí)間:比較字符串時(shí),==比equals()快。對(duì)于兩個(gè)引用變量,只用==判斷引用是否相等,也就可以判斷實(shí)際值是否相等。
歡迎關(guān)注公眾號(hào)"Java學(xué)習(xí)之道",查看更多干貨!
Part2String
2.1不可變性
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
2.2定義方式
字面量的方式
String s1 = "hello";
此時(shí)會(huì)有如下過程。
會(huì)去解析的符號(hào)引用,ldc指令,會(huì)先到字符串常量池中查找是否存在對(duì)應(yīng)字符串實(shí)例的引用,如果有的話,那么直接返回這個(gè)字符串實(shí)例的引用,如果沒有的話,會(huì)創(chuàng)建一個(gè)字符串實(shí)例,那么將其添加到字符串常量池中(實(shí)際上是將其引用放入到一個(gè)哈希表中),之后再返回這個(gè)字符串實(shí)例對(duì)象的引用。
ldc:將int、float、或者一個(gè)類、方法類型或方法句柄的符號(hào)引用、還可能是String型常量值從常量池中推送至棧頂,在執(zhí)行l(wèi)dc指令時(shí)會(huì)觸發(fā)對(duì)它的符號(hào)引用進(jìn)行解析。
new的方式
問題:以下方式會(huì)創(chuàng)建幾個(gè)對(duì)象?怎么證明?
String str = new String("hello");
2個(gè)對(duì)象或者1個(gè)對(duì)象
①如果字符串常量池中已經(jīng)有“hello”,則創(chuàng)建了一個(gè)對(duì)象。如下圖。
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = new String("hello");
System.out.println(s1==s2);
}
}
//輸出:false,由圖容易看出
②如果字符串常量池中不存在“hello”,則創(chuàng)建了兩個(gè)對(duì)象。
一個(gè)對(duì)象是:new關(guān)鍵字在堆空間創(chuàng)建的 另一個(gè)對(duì)象是:另外一個(gè)是在解析常量池的時(shí)候JVM自動(dòng)創(chuàng)建的,如下圖。
歡迎關(guān)注公眾號(hào)"Java學(xué)習(xí)之道",查看更多干貨!

intern()的方式
如果不是用字面量的方式定義的String對(duì)象,可以使用String提供的intern方法:intern方法會(huì)從字符串常量池中查詢當(dāng)前字符串是否存在,若存在則返回其引用;若不存在就會(huì)將當(dāng)前字符串放入常量池中,并返回其引用。我們只需牢記返回的是字符串常量池的引用(即哈希表中的值)即可。
public class Main {
public static void main(String[] args) {
String s1 = new String("1");
String s2=s1.intern();
String s3 = "1";
System.out.println(s2 == s3);
}
}
分析:true
第一行,創(chuàng)建了兩個(gè)對(duì)象實(shí)例,其引用一個(gè)在字符串常量池中,一個(gè)返回給s,如上圖。
第二行,intern()方法會(huì)會(huì)從字符串常量池中查詢當(dāng)前字符串是否存在,發(fā)現(xiàn)存在,返回的是字符串常量池的引用(地址)。
第三行,s3是賦值為字符串常量池的引用。 故 s2和s3地址一樣。
2.3字符串拼接
String s =new String("1")+new String("1");
分析:true
執(zhí)行完成后,堆區(qū)多了兩個(gè)匿名對(duì)象,另外堆區(qū)還多了一個(gè)字面量為11的字符串實(shí)例,并且棧中存在一個(gè)引用指向這個(gè)實(shí)例。當(dāng)我們?cè)谶M(jìn)行字符串拼接時(shí),編譯器默認(rèn)會(huì)創(chuàng)建一個(gè)StringBuilder對(duì)象并調(diào)用其append方法來進(jìn)行拼接,最后再調(diào)用其toString方法來轉(zhuǎn)換成一個(gè)字符串,StringBuilder的toString方法其實(shí)就是new一個(gè)字符串。
//StringBuilder的toString方法
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
-
| 更多精彩文章 -
▽加我微信,交個(gè)朋友 長按/掃碼添加↑↑↑




