java string 常量池_用了这么久Java String,你真的懂字符串常量池吗?
字符串問題可謂是 Java 中經(jīng)久不衰的問題,尤其是字符串常量池經(jīng)常作為面試題出現(xiàn)。可即便是看似簡單而又經(jīng)常被提起的問題,還是有好多同學(xué)一知半解,看上去懂了,仔細(xì)分析起來卻又發(fā)現(xiàn)不太明白。
本文以 JDK 1.8 為討論版本,雖然現(xiàn)在都已經(jīng) JDK 14了,奈何我們還是鐘愛 1.8。
一個(gè)提問引起的討論
為什么說到字符串常量呢,源于群里為數(shù)不多的一個(gè)程序員小姐姐的提問。
這本來和字符串常量沒有關(guān)系,后來,一個(gè)同學(xué)說不只是int,換成String一樣可以。
為什么會(huì)有"Java開發(fā)_北京"這么奇特的字符串亂入呢,因?yàn)樘岢鰡栴}的這位小姐姐的群昵稱叫這個(gè),所以群里的同學(xué)開玩笑說,以為她是某個(gè)房地產(chǎn)大佬,要來開發(fā)北京。
以上是開個(gè)玩笑,好了,收。
字符串用 == 比較也是 true,這就有意思了。馬上有機(jī)靈的小伙伴說這和字符串常量池有關(guān)系。沒錯(cuò),就是因?yàn)樽址A砍氐脑颉?/p>
第一張圖其實(shí)沒什么好說的,在 JDK 1.8 之后已經(jīng)不允許 Object 和 int 類型用 == 相比較了,編譯直接報(bào)錯(cuò)。
第二張圖中的代碼才是重點(diǎn)要說的,我們可以把它簡化成下面這段代碼,用 == 符號(hào)比較字符串,之后的內(nèi)容都從這幾行代碼出發(fā)。
當(dāng)然,實(shí)際開發(fā)中強(qiáng)烈不推薦用 == 符號(hào)判斷兩個(gè)字符串是否相等,應(yīng)該用 equals() 方法。
字符串常量池何許人也
為什么要有字符串常量池呢,像其他對(duì)象一樣直接存在堆中不行嗎,這就要問 Java 語言的設(shè)計(jì)者了,當(dāng)然,這么做也并不是拍腦袋想出來的。
這就要從字符串說起。
首先對(duì)象的分配要付出時(shí)間和空間上的開銷,字符串可以說是和 8 個(gè)基本類型一樣常用的類型,甚至比 8 個(gè)基本類型更加常用,故而頻繁的創(chuàng)建字符串對(duì)象,對(duì)性能的影響是非常大的,所以,用常量池的方式可以很大程度上降低對(duì)象創(chuàng)建、分配的次數(shù),從而提升性能。
在 JDK 1.7 之后(包括1.7),字符串常量池已經(jīng)從方法區(qū)移到了堆中。
字面量賦值
我們把上面的那個(gè)實(shí)例代碼拿過來
String s1 = "古時(shí)的風(fēng)箏";
這是我們平時(shí)聲明字符串變量的最常用的方式,這種方式叫做字面量聲明,也就用把字符串用雙引號(hào)引起來,然后賦值給一個(gè)變量。
這種情況下會(huì)直接將字符串放到字符串常量池中,然后返回給變量。
那這是我再聲明一個(gè)內(nèi)容相同的字符串,會(huì)發(fā)現(xiàn)字符串常量池中已經(jīng)存在了,那直接指向常量池中的地址即可。
例如上圖所示,聲明了 s1 和 s2,到最后都是指向同一個(gè)常量池的地址,所以 s1== s2 的結(jié)果是 true。
new String() 方式
與之對(duì)應(yīng)的是用 new String() 的方式,但是基本上不建議這么用,除非有特殊的邏輯需要。
String a = "古時(shí)的";String s2 = new String(a + "風(fēng)箏");
使用這種方式聲明字符串變量的時(shí)候,會(huì)有兩種情況發(fā)生。
第一種情況,字符串常量池之前已經(jīng)存在相同字符串
比如在使用 new 之前,已經(jīng)用字面量聲明的方式聲明了一個(gè)變量,此時(shí)字符串常量池中已經(jīng)存在了相同內(nèi)容的字符串常量。
首先會(huì)在堆中創(chuàng)建一個(gè) s2 變量的對(duì)象引用;然后將這個(gè)對(duì)象引用指向字符串常量池中的已經(jīng)存在的常量;
第二種情況,字符串常量池中不存在相同內(nèi)容的常量
之前沒有任何地方用到了這個(gè)字符串,第一次聲明這個(gè)字符串就用的是 new String() 的方式,這種情況下會(huì)直接在堆中創(chuàng)建一個(gè)字符串對(duì)象然后返回給變量。
我看到好多地方說,如果字符串常量池中不存在的話,就先把字符串先放進(jìn)去,然后再引用字符串常量池的這個(gè)常量對(duì)象,這種說法是有問題的,只是 new String() 的話,如果池中沒有也不會(huì)放一份進(jìn)去。
基于 new String() 的這種特性,我們可以得出一個(gè)結(jié)論:
以上代碼,肯定輸出的都是 false,因?yàn)?new String() 不管你常量池中有沒有,我都會(huì)在堆中新建一個(gè)對(duì)象,新建出來的對(duì)象,當(dāng)然不會(huì)和其他對(duì)象相等。
intern() 池化
那什么時(shí)候會(huì)放到字符串常量池呢,就是在使用 intern() 方法之后。
intern() 的定義:如果當(dāng)前字符串內(nèi)容存在于字符串常量池,存在的條件是使用 equas() 方法為ture,也就是內(nèi)容是一樣的,那直接返回此字符串在常量池的引用;如果之前不在字符串常量池中,那么在常量池創(chuàng)建一個(gè)引用并且指向堆中已存在的字符串,然后返回常量池中的地址。
第一種情況,準(zhǔn)備池化的字符串與字符串常量池中的字符串有相同(equas()判斷)
這時(shí),這個(gè)字符串常量已經(jīng)在常量池存在了,這時(shí),再 new 了一個(gè)新的對(duì)象 s2,并在堆中創(chuàng)建了一個(gè)相同字符串內(nèi)容的對(duì)象。
這時(shí),s1 == s2 會(huì)返回 fasle。然后我們調(diào)用 s2 = s2.intern(),將池化操作返回的結(jié)果賦值給 s2,就會(huì)發(fā)生如下的變化。
此時(shí),再次判斷 s1 == s2 ,就會(huì)返回 true,因?yàn)樗鼈兌贾赶蛄俗址A砍氐耐粋€(gè)字符串。
第二種情況,字符串常量池中不存在相同內(nèi)容的字符串
使用 new String() 在堆中創(chuàng)建了一個(gè)字符串對(duì)象
圖1
使用了 intern() 之后發(fā)生了什么呢,在常量池新增了一個(gè)對(duì)象,但是 并沒有將字符串復(fù)制一份到常量池,而是直接指向了之前已經(jīng)存在于堆中的字符串對(duì)象。因?yàn)樵?JDK 1.7 之后,字符串常量池不一定就是存字符串對(duì)象的,還有可能存儲(chǔ)的是一個(gè)指向堆中地址的引用,現(xiàn)在說的就是這種情況。注意了,下圖是只調(diào)用了 s2.intern(),并沒有返回給一個(gè)變量。其中字符串常量池(0x88)指向堆中字符串對(duì)象(0x99)就是intern() 的過程。
只有當(dāng)我們把 s2.intern() 的結(jié)果返回給 s2 時(shí),s2 才真正的指向字符串常量池。
我明白了
通過以上的介紹,我們來看下面的一段代碼返回的結(jié)果是什么
【1】:s1 == s2 返回 ture,因?yàn)槎际亲置媪柯暶?#xff0c;全都指向字符串常量池中同一字符串。
【2】: s2 == s3 返回 false,因?yàn)?new String() 是在堆中新建對(duì)象,所以和常量池的常量不相同。
【3】: s3 == s4 返回 false,都是在堆中新建對(duì)象,所以是兩個(gè)對(duì)象,肯定不相同。
【4】: s2 == s3 返回 false,前面雖然調(diào)用了 intern() ,但是沒有返回,不起作用。
【5】: s2 == s3 返回 ture,前面調(diào)用了 intern() ,并且返回給了 s3 ,此時(shí) s2、s3 都直接指向常量池的同一個(gè)字符串。
【6】: s3 == s4 返回 true,和 s3 相同,都指向了常量池同一個(gè)字符串。
為啥我字符串就不可變
字符串常量池的基礎(chǔ)就是字符串的不可變性,如果字符串是可變的,那想一想,常量池就沒必要存在了。假設(shè)多個(gè)變量都指向字符串常量池的同一個(gè)字符串,然后呢,突然來了一行代碼,不管三七二十一,直接把字符串給變了,那豈不是 jvm 世界大亂。
字符串不可變的根本原因應(yīng)該是處于安全性考慮。
我們知道 jvm 類型加載的時(shí)候會(huì)用到類名,比如加載 java.lang.String 類型,如果字符串可變的話,那我替換成其他的字符,那豈不是很危險(xiǎn)。
項(xiàng)目中會(huì)用到比如數(shù)據(jù)庫連接串、賬號(hào)、密碼等字符串,只有不可變的連接串、用戶名和密碼才能保證安全性。
字符串在 Java 中的使用頻率可謂高之又高,那在高并發(fā)的情況下不可變性也使得對(duì)字符串的讀寫操作不用考慮多線程競爭的情況。
還有就是 HashCode,HashCode 是判斷兩個(gè)對(duì)象是否完全相等的核心條件,另外,像 Set、Map 結(jié)構(gòu)中的 key 值也需要用到 HashCode 來保證唯一性和一致性,因此不可變的 HashCode 才是安全可靠的。
最后一點(diǎn)就是上面提到的,字符串對(duì)象的頻繁創(chuàng)建會(huì)帶來性能上的開銷,所以,利用不可變性才有了字符串常量池,使得性能得以保障。
總結(jié)
以上是生活随笔為你收集整理的java string 常量池_用了这么久Java String,你真的懂字符串常量池吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java线程内存模型_深度解析Java多
- 下一篇: 标记注解 java_【java】细说 J