一文帶你了解如何正確使用Java中的字符串常量池
前言
研究表明,Java堆中對象占據(jù)最大比重的就是字符串對象,所以弄清楚字符串知識很重要,本文主要重點聊聊字符串常量池。Java中的字符串常量池是Java堆中的一塊特殊存儲區(qū)域,用于存儲字符串。它的實現(xiàn)是為了提高字符串操作的性能并節(jié)省內(nèi)存。它也被稱為String Intern Pool
或String Constant Pool
。那讓我來看看究竟是怎么一回事吧。
理解字符串常量池
當您從在類中寫一個字符串字面量時,JVM將首先檢查該字符串是否已存在于字符串常量池中,如果存在,JVM 將返回對現(xiàn)有字符串對象的引用,而不是創(chuàng)建新對象。我們通過一個例子更好的來理解。
比如下面的代碼:
String s1 = "Harry Potter"; String s2 = "The Lord of the Rings"; String s3 = "Harry Potter";
在這段代碼中,JVM 將創(chuàng)建一個值為“Harry Potter
”的字符串對象,并將其存儲在字符串常量池中。s1和s3都將是對該單個字符串對象的引用。
如果s2的字符串內(nèi)容“The Lord of the Rings
”不存在于池中,則在字符串池中生成一個新的字符串對象。
兩種創(chuàng)建字符串方式
在 Java
編程語言中有兩種創(chuàng)建 String
的方法。第一種方式是使用String Literal
字符串字面量的方式,另一種方式是使用new
關(guān)鍵字。他們創(chuàng)建的字符串對象是都在常量池中嗎?
字符串字面量的方式創(chuàng)建
String s1 = "Harry Potter"; String s2 = "The Lord of the Rings"; String s3 = "Harry Potter";
new
關(guān)鍵字創(chuàng)建
String s4 = new String("Harry Potter"); String s5 = new String("The Lord of the Rings");
我們來比較下他們引用的是否是同一個對象:
s1==s3 //真 s1==s4 //假 s2==s5 //假
使用 == 運算符比較兩個對象時,它會比較內(nèi)存中的地址。
正如您在上面的圖片和示例中看到的,每當我們使用new
運算符創(chuàng)建字符串時,它都會在 Java 堆中創(chuàng)建一個新的字符串對象,并且不會檢查該對象是否在字符串常量池中。
那么我現(xiàn)在有個問題,如果是字符串拼接的情況,又是怎么樣的呢?
字符串拼接方式
前面講清楚了通過直接用字面量的方式,也就是引號的方式和用new關(guān)鍵字創(chuàng)建字符串,他們創(chuàng)建出的字符串對象在堆中存儲在不同的地方,那么我們現(xiàn)在來看看用+
這個運算符拼接會怎么樣。
例子1
public static void test1() { // 都是常量,前端編譯期會進行代碼優(yōu)化 // 通過idea直接看對應(yīng)的反編譯的class文件,會顯示 String s1 = "abc"; 說明做了代碼優(yōu)化 String s1 = "a" + "b" + "c"; String s2 = "abc"; // true,有上述可知,s1和s2實際上指向字符串常量池中的同一個值 System.out.println(s1 == s2); }
常量與常量的拼接結(jié)果在常量池,原理是編譯期優(yōu)化。
例子2
public static void test5() { String s1 = "javaEE"; String s2 = "hadoop"; String s3 = "javaEEhadoop"; String s4 = "javaEE" + "hadoop"; String s5 = s1 + "hadoop"; String s6 = "javaEE" + s2; String s7 = s1 + s2; System.out.println(s3 == s4); // true 編譯期優(yōu)化 System.out.println(s3 == s5); // false s1是變量,不能編譯期優(yōu)化 System.out.println(s3 == s6); // false s2是變量,不能編譯期優(yōu)化 System.out.println(s3 == s7); // false s1、s2都是變量 System.out.println(s5 == s6); // false s5、s6 不同的對象實例 System.out.println(s5 == s7); // false s5、s7 不同的對象實例 System.out.println(s6 == s7); // false s6、s7 不同的對象實例 }
只要其中有一個是變量,結(jié)果就在堆中, 變量拼接的底層原理其實是StringBuilder
。
例子3:
public void test6(){ String s0 = "beijing"; String s1 = "bei"; String s2 = "jing"; String s3 = s1 + s2; System.out.println(s0 == s3); // false s3指向?qū)ο髮嵗瑂0指向字符串常量池中的"beijing" String s7 = "shanxi"; final String s4 = "shan"; final String s5 = "xi"; String s6 = s4 + s5; System.out.println(s6 == s7); // true s4和s5是final修飾的,編譯期就能確定s6的值了 }
- 不使用final修飾,即為變量。如s3行的s1和s2,會通過new StringBuilder進行拼接
- 使用final修飾,即為常量。會在編譯器進行代碼優(yōu)化。
妙用String.intern() 方法
前面提到new關(guān)鍵字創(chuàng)建出來的字符串對象以及某些和變量進行拼接不會在字符串常量池中,而是直接在堆中新建了一個對象。這樣不大好,做不到復(fù)用,節(jié)約不了空間。那有什么好辦法呢?intern()
就派上用場了,這個非常有用。
intern()
方法的作用可以理解為主動將常量池中還沒有的字符串對象放入池中,并返回此對象地址。
String s6 = new String("The Lord of the Rings").intern();
s2==s6 //真 s2==s5 //假
字符串常量池有多大
關(guān)于字符串常量池究竟有多大,我也說不上來,但是講清楚它底層的數(shù)據(jù)結(jié)構(gòu),也許你就明白了。
字符串常量池是一個固定大小的HashTable
,哈希表,默認值大小長度是1009
。如果放進String Pool
的String
非常多,就會造成Hash
沖突嚴重,從而導(dǎo)致鏈表會很長,而鏈表長了后直接會造成的影響就是當調(diào)用String.intern
時性能會大幅下降。
使用-XX:StringTablesize
可設(shè)置StringTable
的長度
- 在jdk6中
StringTable
是固定的,就是1009
的長度,所以如果常量池中的字符串過多就會導(dǎo)致效率下降很快。StringTable Size
設(shè)置沒有要求 - 在jdk7中,StringTable的長度默認值是
60013
,StringTable Size
設(shè)置沒有要求
在jdk8中,設(shè)置StringTable
長度的話,1009
是可以設(shè)置的最小值
字符串常量池的優(yōu)缺點
字符串池的優(yōu)點
提高性能。由于 JVM 可以返回對現(xiàn)有字符串對象的引用而不是創(chuàng)建新對象,因此使用字符串池時字符串操作更快。
共享字符串,節(jié)省內(nèi)存。字符串池允許您在不同的變量和對象之間共享字符串,通過避免創(chuàng)建不必要的字符串對象來幫助節(jié)省內(nèi)存。
字符串池的缺點
它有可能導(dǎo)致性能下降。從池中檢索字符串需要搜索池中的所有字符串,這可能比簡單地創(chuàng)建一個新的字符串對象要慢。如果程序創(chuàng)建和丟棄大量字符串,則尤其如此,因為每次使用字符串時都需要搜索字符串池。
總結(jié)
其實在 Java 7 之前,JVM將 Java String Pool
放置在PermGen
空間中,它具有固定大小——它不能在運行時擴展,也不符合垃圾回收的條件。在PermGen
(而不是堆)中駐留字符串的風險是,如果我們駐留太多字符串,我們可能會從 JVM 得到一個OutOfMemory
錯誤。從 Java 7 開始,Java String Pool
存放在Heap空間
,由 JVM進行垃圾回收。這種方法的優(yōu)點是降低了OutOfMemory
錯誤的風險,因為未引用的字符串將從池中刪除,從而釋放內(nèi)存。
現(xiàn)在通過本文的學(xué)習(xí),你該知道如何更好的創(chuàng)建字符串對象了吧。
到此這篇關(guān)于一文帶你了解如何正確使用Java中的字符串常量池的文章就介紹到這了,更多相關(guān)Java字符串常量池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java:程序包org.springframework.boot不存在的完美解決方法
最近項目中運行的時候提示了"java: 程序包org.springframework.boot不存在",下面這篇文章主要給大家介紹了關(guān)于java:程序包org.springframework.boot不存在的完美解決方法,需要的朋友可以參考下2023-05-05Java中l(wèi)ambda表達式實現(xiàn)aop切面功能
本文主要介紹了Java中l(wèi)ambda表達式實現(xiàn)aop切面功能,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02java框架基礎(chǔ)之SPI機制實現(xiàn)及源碼解析
這篇文章主要為大家介紹了java框架基礎(chǔ)之SPI機制實現(xiàn)及源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09springboot中server.ssl.key-store配置路徑的問題小結(jié)
這篇文章主要介紹了springboot中server.ssl.key-store配置路徑的問題,文中還記錄了Spring Boot SSL(https)實例,介紹在web程序中使用自簽名的SSL(HTTPS)證書及創(chuàng)建SSL認證,感興趣的朋友跟隨小編一起看看吧2024-02-02