我是風箏,公衆號「古時的風箏」,一個不僅有技術的技術公衆號,一個在程序圈混跡多年,主業 Java,另外 Python、React 也玩兒的 6 的斜槓開發者。 Spring Cloud 系列文章已經完成,能夠到 個人 github 上查看系列完整內容。也能夠在公衆號內回覆「pdf」獲取我精心製做的 pdf 版完整教程。java
字符串問題可謂是 Java 中經久不衰的問題,尤爲是字符串常量池常常做爲面試題出現。可即使是看似簡單而又常常被提起的問題,仍是有好多同窗只知其一;不知其二,看上去懂了,仔細分析起來卻有發現不太明白。git
本文以 JDK 1.8 爲討論版本,雖然如今都已經 JDK 14了,奈何咱們仍是鍾愛 1.8。程序員
爲何說到字符串常量呢,源於羣裏爲數很少的一個程序員小姐姐的提問。github
這原本和字符串常量沒有關係,後來,一個同窗說不僅是 int ,換成 String 同樣能夠。web
爲何會有"Java開發_北京"這麼奇特的字符串亂入呢,由於提出問題的這位小姐姐的羣暱稱叫這個,因此羣裏的同窗開玩笑說,覺得她是某個房地產大佬,要來開發北京。面試
以上是開個玩笑,好了,收。spring
字符串用 == 比較也是 true,這就有意思了。立刻有機靈的小夥伴說這和字符串常量池有關係。沒錯,就是由於字符串常量池的緣由。數據庫
第一張圖其實沒什麼好說的,在 JDK 1.8 以後已經不容許 Object 和 int 類型用 == 相比較了,編譯直接報錯。安全
第二張圖中的代碼纔是重點要說的,咱們能夠把它簡化成下面這段代碼,用 == 符號比較字符串,以後的內容都從這幾行代碼出發。多線程
public static void main(String[] args) {
String s1 = "古時的風箏";
System.out.println(s1 == "古時的風箏");
}
複製代碼
固然,實際開發中強烈不推薦用 == 符號判斷兩個字符串是否相等,應該用 equals() 方法。
爲何要有字符串常量池呢,像其餘對象同樣直接存在堆中不行嗎,這就要問 Java 語言的設計者了,固然,這麼作也並非拍腦殼想出來的。
這就要從字符串提及。
首先對象的分配要付出時間和空間上的開銷,字符串能夠說是和 8 個基本類型同樣經常使用的類型,甚至比 8 個基本類型更加經常使用,故而頻繁的建立字符串對象,對性能的影響是很是大的,因此,用常量池的方式能夠很大程度上下降對象建立、分配的次數,從而提高性能。
在 JDK 1.7 以後(包括1.7),字符串常量池已經從方法區移到了堆中。
咱們把上面的那個實例代碼拿過來
String s1 = "古時的風箏";
複製代碼
這是咱們平時聲明字符串變量的最經常使用的方式,這種方式叫作字面量聲明,也就用把字符串用雙引號引發來,而後賦值給一個變量。
這種狀況下會直接將字符串放到字符串常量池中,而後返回給變量。
那這是我再聲明一個內容相同的字符串,會發現字符串常量池中已經存在了,那直接指向常量池中的地址便可。
例如上圖所示,聲明瞭 s1 和 s2,到最後都是指向同一個常量池的地址,因此 s1== s2 的結果是 true。
與之對應的是用 new String() 的方式,可是基本上不建議這麼用,除非有特殊的邏輯須要。
String s2 = new String("古時的風箏");
複製代碼
使用這種方式聲明字符串變量的時候,會有兩種狀況發生。
好比在使用 new 以前,已經用字面量聲明的方式聲明瞭一個變量,此時字符串常量池中已經存在了相同內容的字符串常量。
以前沒有任何地方用到了這個字符串,第一次聲明這個字符串就用的是 new String() 的方式,這種狀況下會直接在堆中建立一個字符串對象而後返回給變量。
我看到好多地方說,若是字符串常量池中不存在的話,就先把字符串先放進去,而後再引用字符串常量池的這個常量對象,這種說法是有問題的,只是 new String() 的話,若是池中沒有也不會放一份進去。
基於 new String() 的這種特性,咱們能夠得出一個結論:
String s1 = "古時的風箏";
String s2 = new String("古時的風箏");
String s3 = new String("古時的風箏");
System.out.println(s1==s2); // false
System.out.println(s2==s3); // false
複製代碼
以上代碼,確定輸出的都是 false,由於 new String() 無論你常量池中有沒有,我都會在堆中新建一個對象,新建出來的對象,固然不會和其餘對象相等。
那何時會放到字符串常量池呢,就是在使用 intern() 方法以後。
intern() 的定義:若是當前字符串內容存在於字符串常量池,存在的條件是使用 equas() 方法爲ture,也就是內容是同樣的,那直接返回此字符串在常量池的引用;若是以前不在字符串常量池中,那麼在常量池建立一個引用而且指向堆中已存在的字符串,而後返回常量池中的地址。
String s1 = "古時的風箏";
String s2 = new String("古時的風箏");
s2 = s2.intern();
複製代碼
這時,這個字符串常量已經在常量池存在了,這時,再 new 了一個新的對象 s2,並在堆中建立了一個相同字符串內容的對象。
這時,s1 == s2 會返回 fasle。而後咱們調用 s2 = s2.intern(),將池化操做返回的結果賦值給 s2,就會發生以下的變化。
此時,再次判斷 s1 == s2 ,就會返回 true,由於它們都指向了字符串常量池的同一個字符串。
使用 new String() 在堆中建立了一個字符串對象
使用了 intern() 以後發生了什麼呢,在常量池新增了一個對象,可是 並無 將字符串複製一份到常量池,而是直接指向了以前已經存在於堆中的字符串對象。由於在 JDK 1.7 以後,字符串常量池不必定就是存字符串對象的,還有可能存儲的是一個指向堆中地址的引用,如今說的就是這種狀況,注意了,下圖是隻調用了 s2.intern()
,並無返回給一個變量。其中字符串常量池(0x88)指向堆中字符串對象(0x99)就是intern() 的過程。
只有當咱們把 s2.intern() 的結果返回給 s2 時,s2 才真正的指向字符串常量池。
經過以上的介紹,咱們來看下面的一段代碼返回的結果是什麼
public class Test {
public static void main(String[] args) {
String s1 = "古時的風箏";
String s2 = "古時的風箏";
String s3 = new String("古時的風箏");
String s4 = new String("古時的風箏");
System.out.println(s1 == s2); // 【1】 true
System.out.println(s2 == s3); // 【2】 false
System.out.println(s3 == s4); // 【3】 false
s3.intern();
System.out.println(s2 == s3); // 【4】 false
s3 = s3.intern();
System.out.println(s2 == s3); // 【5】 true
s4 = s4.intern();
System.out.println(s3 == s4); // 【6】 true
}
}
複製代碼
【1】:s1 == s2 返回 ture,由於都是字面量聲明,全都指向字符串常量池中同一字符串。
【2】: s2 == s3 返回 false,由於 new String() 是在堆中新建對象,因此和常量池的常量不相同。
【3】: s3 == s4 返回 false,都是在堆中新建對象,因此是兩個對象,確定不相同。
【4】: s2 == s3 返回 false,前面雖然調用了 intern() ,可是沒有返回,不起做用。
【5】: s2 == s3 返回 ture,前面調用了 intern() ,而且返回給了 s3 ,此時 s二、s3 都直接指向常量池的同一個字符串。
【6】: s3 == s4 返回 true,和 s3 相同,都指向了常量池同一個字符串。
字符串常量池的基礎就是字符串的不可變性,若是字符串是可變的,那想想,常量池就不必存在了。假設多個變量都指向字符串常量池的同一個字符串,而後呢,忽然來了一行代碼,無論三七二十一,直接把字符串給變了,那豈不是 jvm 世界大亂。
字符串不可變的根本緣由應該是處於安全性考慮。
咱們知道 jvm 類型加載的時候會用到類名,好比加載 java.lang.String 類型,若是字符串可變的話,那我替換成其餘的字符,那豈不是很危險。
項目中會用到好比數據庫鏈接串、帳號、密碼等字符串,只有不可變的鏈接串、用戶名和密碼才能保證安全性。
字符串在 Java 中的使用頻率可謂高之又高,那在高併發的狀況下不可變性也使得對字符串的讀寫操做不用考慮多線程競爭的狀況。
還有就是 HashCode,HashCode 是判斷兩個對象是否徹底相等的核心條件,另外,像 Set、Map 結構中的 key 值也須要用到 HashCode 來保證惟一性和一致性,所以不可變的 HashCode 纔是安全可靠的。
最後一點就是上面提到的,字符串對象的頻繁建立會帶來性能上的開銷,因此,利用不可變性纔有了字符串常量池,使得性能得以保障。
知其然,也要知因此然。只知其一;不知其二纔不是咱們追求的目標。不知道圖畫的夠不夠清晰,但願能幫助到對字符串常量池不甚瞭解的同窗。
我是風箏,公衆號「古時的風箏」,一個在程序圈混跡多年,主業 Java,另外 Python、React 也玩兒的很 6 的斜槓開發者。能夠在公衆號中加我好友,進羣裏小夥伴交流學習,好多大廠的同窗也在羣內呦。