1、字符串常量池
1.1 創建對象的思考
下面是兩種創建字符串對象的代碼
public static void main1(String[] args) {String s1 = "hello";String s2 = "hello";System.out.println(s1 == s2);//trueString s3 = new String("hello");String s4 = new String("hello");System.out.println(s1 == s3);//falseSystem.out.println(s3 == s4);//false}
上述程序創建方式類似,但為什么打印結果s1 == s2的時候是true,s1 == s3打印的時候是false,s3 == s4打印的時候是false呢?
在Java中,對于1、2、3、3.14、“hello”等字面類型的常量經常頻繁使用,為了使程序運行更快,更節省內存,Java為8種基本類型和String類都提供了常量池。
"池" 是編程中的一種常見的, 重要的提升效率的方式, 我們會在未來的學習中遇到各種 "內存池", "線程池", "數 據庫連接池" .... 比如:家里給大家打生活費的方式 1. 家里經濟拮據,每月定時打生活費,有時可能會晚,最差情況下可能需要向家里張口要,速度慢 2. 家里有礦,一次性打一年的生活費放到銀行卡中,自己隨用隨取,速度非常快 方式2,就是池化技術的一種示例,錢放在卡上,隨用隨取,效率非常高。常見的池化技術比如:數據庫連接 池、線程池等。
為了節省程序存儲的空間并且提高程序的運行效率,Java種引入了:
1、Class文件常量池:每個Java源文件編譯后生成.class文件中會保存當前類中的字面常量以及符號信息。
2、運行時常量池:在.class文件被加載時候,.class文件中的常量池被加載到內存中稱之為運行時常量池,運行時常量池每個類都有一份。
3、字符常量池:其具體概念會在講解JVM時候詳細解釋。
1.2 字符串常量池
字符串常量池在Java的官方文檔中并未詳細說明,只是有一個在JVM中的StringTable類,實際上,是一個固定大小是HashTable(一種高效用來進行查找的數據結構),不同JDK版本下字符串常量池的位置以及默認大小是不同的:
1.3?字符串常量池的創建
如下代碼,創建字符串就會先在字符串常量池先進行創建:
public static void main(String[] args){String s1 = "hello";String s2 = "hello";System.out.println(s1 == s2);//true
}
那么創建過程是什么呢?下面通過畫圖進行講解:
JVM中存在棧和堆。
當執行代碼String s1 = "hello"的時候,堆中有一個StringTable類,其中是一個HashTable,在HashTable中存儲的是一些鏈表的地址。當我們String s1 = "hello"的時候,常量池中會有一個空間來指向一個鏈表,這個鏈表會再指向一個String對象,這個String對象的value會指向一個字符數組,從而存儲起來hello。
?
同時,棧中開辟一塊空間存放0x11。?這就是一次將常量放入常量池的過程。
存儲字符串常量的時候,會先檢查當前的常量池中 是否存在所要存儲的常量,如果有的話,就不會再放入常量池中!
我們剛剛定義了String s2 = "hello",則先會再常量池中,尋找一下,是否有“hello”的常量,檢查發現有,則直接再在棧中開辟一塊空間給s2,但因為s2所要存儲的常量以及在常量池中了,所以s2的地址直接指向常量池中是String對象即可。
所以在打印s1==s2的時候是true。
但如果是s3的創建方式:new String("hello")。?創建結果如圖所示:
?即只要有new一個對象的發生,就一定會在堆中創建一個對象,則s4指向的地址為0x999的對象的地址,雖然也可以找到地址為0x88的hello,但s1和s3所存的地址就不一樣了,所以在打印的時候,會出現s1?== s3的結果為false。
通過上面的例子可以看出:使用常量串創建String類型對象的效率更高,而且更節省空間。
1.4 intern方法
有如下代碼:
public static void main(String[] args) {char[] ch = {'a','b','c'};String s1 = new String(ch);String s2 = "abc";System.out.println(s2 == s1);}
通過上面內容的學習:我們知道輸出的結果一定是false。
過程如下:
char[] ch = new char[]{'a', 'b', 'c'};之后,會在堆里有一個字符數組0x87,然后ch中存儲地址0x87。
然后String s1 = new String(ch); 和上面一樣,new出了新是對象String。
執行String s2 = "abc"的時候,過程如下,將 a b c放入常量池中?。
此時s1 和 s2指向的地址并不相同,索引打印結果自然是false。
但如果在String s1 = new String(ch)的代碼之后,添加一行代碼:s1.intern(),就可以實現將s1對象的引用放入到常量池。
public static void main(String[] args) {char[] ch = {'a','b','c'};String s1 = new String(ch);String s2 = "abc";s1.intern();//1、先檢查s1所指向的對象是否在常量池2、有則不入池,沒有則入池System.out.println(s2 == s1);}
如果加上了這行代碼,過程就會如下圖所示,結果返回true:?intern方法:
1.會先檢查調用該方法所指的對象是否在常量池中存在。
2.如果有則不入池。
3.如果沒有則入池。