日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

理解Java字符串常量池与intern()方法

發布時間:2024/10/5 java 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 理解Java字符串常量池与intern()方法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

理解Java字符串常量池與intern()方法

閱讀目錄

  • Java內存區域?
  • 兩種創建方式在內存中的區別
  • 解釋開頭的例子
  • intern()方法
  • 參考資料

String s1 = "Hello"; String s2 = "Hello"; String s3 = "Hel" + "lo"; String s4 = "Hel" + new String("lo"); String s5 = new String("Hello"); String s6 = s5.intern(); String s7 = "H"; String s8 = "ello"; String s9 = s7 + s8;System.out.println(s1 == s2); // true System.out.println(s1 == s3); // true System.out.println(s1 == s4); // false System.out.println(s1 == s9); // false System.out.println(s4 == s5); // false System.out.println(s1 == s6); // true

剛開始看字符串的時候,經常會看到類似的題,難免會有些不解,查看答案總會提到字符串常量池、運行常量池等概念,很容易讓人搞混。

下面就來說說Java中的字符串到底是怎樣創建的。

回到頂部

Java內存區域?

String有兩種賦值方式,第一種是通過“字面量”賦值。

String str = "Hello";

?第二種是通過new關鍵字創建新對象。

String str = new String("Hello");

?要弄清楚這兩種方式的區別,首先要知道他們在內存中的存儲位置。

圖片來源:http://286.iteye.com/blog/1928180

我們平時所說的內存就是圖中的運行時數據區(Runtime Data Area),其中與字符串的創建有關的是方法區(Method Area)堆區(Heap Area)棧區(Stack Area)

  • 方法區:存儲類信息、常量、靜態變量。全局共享。
  • 堆區:存放對象和數組。全局共享。
  • 棧區:基本數據類型、對象的引用都存放在這。線程私有。
  • 每當一個方法被執行時就會在棧區中創建一個棧幀(Stack Frame),基本數據類型和對象引用就存在棧幀中局部變量表(Local Variables)

    當一個類被加載之后,類信息就存儲在非堆的方法區中。在方法區中,有一塊叫做運行時常量池(Runtime Constant Pool),它是每個類私有的,每個class文件中的“常量池”被加載器加載之后就映射存放在這,后面會說到這一點。

    和String最相關的是字符串池(String Pool),其位置在方法區上面的駐留字符串(Interned Strings)的位置,之前一直把它和運行時常量池搞混,其實是兩個完全不同的存儲區域,字符串常量池是全局共享的。字符串調用String.intern()方法后,其引用就存放在String Pool中。

    回到頂部

    兩種創建方式在內存中的區別

    了解了這些概念,下面來說說究竟兩種字符串創建方式有何區別。

    下面的Test類,在main方法里以“字面量”賦值的方式給字符串str賦值為“Hello”。

    public class Test {public static void main(String[] args) {String str = "Hello";} }

    Test.java文件編譯后得到.class文件,里面包含了類的信息,其中有一塊叫做常量池(Constant Pool)的區域,.class常量池和內存中的常量池并不是一個東西。

    .class文件常量池主要存儲的就包括字面量,字面量包括類中定義的常量,由于String是不可變的(String為什么是不可變的?),所以字符串“Hello”就存放在這。

    當程序用到Test類時,Test.class被解析到內存中的方法區。.class文件中的常量池信息會被加載到運行時常量池,但String不是。

    例子中“Hello”會在堆區中創建一個對象,同時會在字符串池(String Pool)存放一個它的引用,如下圖所示。

    此時只是Test類剛剛被加載,主函數中的str并沒有被創建,而“Hello”對象已經創建在于堆中。

    當主線程開始創建str變量的,虛擬機會去字符串池中找是否有equals(“Hello”)的String,如果相等就把在字符串池中“Hello”的引用復制給str。如果找不到相等的字符串,就會在堆中新建一個對象,同時把引用駐留在字符串池,再把引用賦給str。

    當用字面量賦值的方法創建字符串時,無論創建多少次,只要字符串的值相同,它們所指向的都是堆中的同一個對象。

    public class Test {public static void main(String[] args) {String str1 = "Hello";String str2 = “Hello”;String str3 = “Hello”;} }

    當利用new關鍵字去創建字符串時,前面加載的過程是一樣的,只是在運行時無論字符串池中有沒有與當前值相等的對象引用,都會在堆中新開辟一塊內存,創建一個對象。

    public class Test {public static void main(String[] args) {String str1 = "Hello";String str2 = “Hello”;String str3 = new String("Hello");} }

    回到頂部

    解釋開頭的例子

    現在我們來回頭看之前的例子。

    String s1 = "Hello"; String s2 = "Hello"; String s3 = "Hel" + "lo"; String s4 = "Hel" + new String("lo"); String s5 = new String("Hello"); String s6 = s5.intern(); String s7 = "H"; String s8 = "ello"; String s9 = s7 + s8;System.out.println(s1 == s2); // true System.out.println(s1 == s3); // true System.out.println(s1 == s4); // false System.out.println(s1 == s9); // false System.out.println(s4 == s5); // false System.out.println(s1 == s6); // true

    ?有了上面的基礎,之前的問題就迎刃而解了。

    s1在創建對象的同時,在字符串池中也創建了其對象的引用。

    由于s2也是利用字面量創建,所以會先去字符串池中尋找是否有相等的字符串,顯然s1已經幫他創建好了,它可以直接使用其引用。那么s1和s2所指向的都是同一個地址,所以s1==s2

    s3是一個字符串拼接操作,參與拼接的部分都是字面量,編譯器會進行優化,在編譯時s3就變成“Hello”了,所以s1==s3

    s4雖然也是拼接,但“lo”是通過new關鍵字創建的,在編譯期無法知道它的地址,所以不能像s3一樣優化。所以必須要等到運行時才能確定,必然新對象的地址和前面的不同。

    同理,s9由兩個變量拼接,編譯期也不知道他們的具體位置,不會做出優化。

    s5是new出來的,在堆中的地址肯定和s4不同。

    s6利用intern()方法得到了s5在字符串池的引用,并不是s5本身的地址。由于它們在字符串池的引用都指向同一個“Hello”對象,自然s1==s6

    ?總結一下:

    • 字面量創建字符串會先在字符串池中找,看是否有相等的對象,沒有的話就在堆中創建,把地址駐留在字符串池;有的話則直接用池中的引用,避免重復創建對象。
    • new關鍵字創建時,前面的操作和字面量創建一樣,只不過最后在運行時會創建一個新對象,變量所引用的都是這個新對象的地址。

    ?由于不同版本的JDK內存會有些變化,JDK1.6字符串常量池在永久代,1.7移到了堆中,1.8用元空間代替了永久代。但是基本對上面的結論沒有影響,思想是一樣的。

    回到頂部

    intern()方法

    下面來說說跟字符常量池有關的intern()方法。

    /*** Returns a canonical representation for the string object.* <p>* A pool of strings, initially empty, is maintained privately by the* class {@code String}.* <p>* When the intern method is invoked, if the pool already contains a* string equal to this {@code String} object as determined by* the {@link #equals(Object)} method, then the string from the pool is* returned. Otherwise, this {@code String} object is added to the* pool and a reference to this {@code String} object is returned.* <p>* It follows that for any two strings {@code s} and {@code t},* {@code s.intern() == t.intern()} is {@code true}* if and only if {@code s.equals(t)} is {@code true}.* <p>* All literal strings and string-valued constant expressions are* interned. String literals are defined in section 3.10.5 of the* <cite>The Java&trade; Language Specification</cite>.** @return a string that has the same contents as this string, but is* guaranteed to be from a pool of unique strings.*/public native String intern();

    這個方法是一個本地方法,注釋中描述得很清楚:“如果常量池中存在當前字符串,就會直接返回當前字符串;如果常量池中沒有此字符串,會將此字符串放入常量池中后,再返回”。

    由于上面提到JDK1.6之后,字符串常量池在內存中的位置發生了變化,所以intern()方法在不同版本的JDK中也有所差別。

    來看下面的代碼:

    public static void main(String[] args) {String s = new String("1");s.intern();String s2 = "1";System.out.println(s == s2);String s3 = new String("1") + new String("1");s3.intern();String s4 = "11";System.out.println(s3 == s4); }

    在JDK1.6中的運行結果是false false,而在1.7中結果是false true

    將intern()語句下移一行。

    public static void main(String[] args) {String s = new String("1");String s2 = "1";s.intern();System.out.println(s == s2);String s3 = new String("1") + new String("1");String s4 = "11";s3.intern();System.out.println(s3 == s4); }

    在JDK1.6中的運行結果是false false,在1.7中結果也是false false

    下面來解釋一下JDK1.6環境下的結果:

    圖中綠色線條代表string對象的內容指向,黑色線條代表地址指向。

    JDK1.6中的intern()方法只是返回常量池中的引用,上面說過,常量池中的引用所指向的對象和new出來的對象并不是一個,所以他們的地址自然不相同。

    接著來說一下JDK1.7

    再貼一下代碼

    public static void main(String[] args) {String s = new String("1");s.intern();String s2 = "1";System.out.println(s == s2);String s3 = new String("1") + new String("1");s3.intern();String s4 = "11";System.out.println(s3 == s4); }

    創建s3生成了兩個最終對象(不考慮兩個new String("1"),常量池中也沒有“11”),一個是s3,另一個是池中的“1”。如果在1.6中,s3調用intern()方法,則先在常量池中尋找是否有等于“11”的對象,本例中自然是沒有,然后會在堆中創建一個“11”的對象,并在常量池中存儲它的引用并返回。然而在1.7中調用intern(),如果這個字符串在常量池中是第一次出現,則不會重新創建對象,直接返回它在堆中的引用。在本例中,s4和s3指向的都是在堆中的那個對象,所以s3和s4的地址相等。

    由于s是new出來的,所以會在常量池和堆中創建兩個不同的對象,s.intern()后,發現“1”并不是第一次出現在常量池了,所以接下來就和之前沒有區別了。

    public static void main(String[] args) {String s = new String("1");String s2 = "1";s.intern();System.out.println(s == s2);String s3 = new String("1") + new String("1");String s4 = "11";s3.intern();System.out.println(s3 == s4); }

    將intern()語句下移一行后,執行順序發生改變,執行到intern()時,字符串常量池已經存在“1”和“11”了,并不是第一次出現,字面量對象依然指向的是常量池,所以字面量創建的對象和new的對象地址一定是不同的。


    轉載請注明原文鏈接:http://www.cnblogs.com/justcooooode/p/7603381.html

    回到頂部

    參考資料

    https://www.zhihu.com/question/29884421/answer/113785601

    http://www.cnblogs.com/iyangyuan/p/4631696.html

    https://tech.meituan.com/in_depth_understanding_string_intern.html

    https://javaranch.com/journal/200409/ScjpTipLine-StringsLiterally.html ——【譯】Java中的字符串字面量

    作者:沒課割綠地

    出處:http://www.cnblogs.com/justcooooode

    ?

    與50位技術專家面對面20年技術見證,附贈技術全景圖

    總結

    以上是生活随笔為你收集整理的理解Java字符串常量池与intern()方法的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。