字符串常量池深入解析


 

字符串常量池

 

 

概述

在分析字符串常量池之前,先來分析一下java的內存區域,然后再各種的情況分析一下各種情況下的情況;

mark

在《深入理解java虛擬機》這本書上是這樣寫的:對於HotSpot虛擬機,根據官方發布的路線圖信息,現在也有放棄永久代並逐步的改為采用Native Memory來實現方法區的規划了,在目前已經發布的JDK1.7的HotSpot中,已經把原來存放在方法區中的字符串常量池移出。根據查閱的資料顯示在JDK1.7以后的版本中字符串常量池移到堆內存區域;同時在jdk1.8中移除整個永久代,取而代之的是一個叫元空間(Metaspace)的區域

在 JAVA 語言中有8中基本類型和一種比較特殊的類型String。這些類型為了使他們在運行過程中速度更快,更節省內存,都提供了一種常量池的概念。常量池就類似一個JAVA系統級別提供的緩存。

8種基本類型的常量池都是系統協調的,String類型的常量池比較特殊。它的主要使用方法有兩種

  • 直接使用雙引號聲明出來的String對象會直接存儲在常量池中。
  • 如果不是用雙引號聲明的String對象,可以使用String提供的intern方法。intern 方法會從字符串常量池中查詢當前字符串是否存在,若不存在就會將當前字符串放入常量池中

一、Java中兩種創建字符串對象的方式的分析。

String s1 = "abc";
String s2 = "abc";
System.out.println(s1==s2);

結果是 true;

采用字面值的方式創建一個字符串時,JVM首先會去字符串池中查找是否存在"abc"這個對象,如果不存在,則在字符串常量池中創建"abc"這個對象,然后將池中"abc"這個對象的引用地址返回給"abc"對象的引用s1,這樣s1會指向字符串常量池中"abc"這個字符串對象;如果存在,則不創建任何對象,直接將池中"abc"這個對象的地址返回,賦給引用s2。因為s1、s2都是指向同一個字符串池中的"abc"對象,所以結果為true。

String s3 = new String("xyz");
String s4 = new String("xyz");
System.out.println(s3==s4);

結果是 false

采用new關鍵字新建一個字符串對象時,JVM首先在字符串池中查找有沒有"xyz"這個字符串對象,如果有,則不在池中再去創建"xyz"這個對象了,直接在堆中創建一個"xyz"字符串對象,然后將堆中的這個"xyz"對象的地址返回賦給引用s3,這樣,s3就指向了堆中創建的這個"xyz"字符串對象;如果沒有,則首先在字符串池中創建一個"xyz"字符串對象,然后再在堆中創建一個"xyz"字符串對象,然后將堆中這個"xyz"字符串對象的地址返回賦給s3引用,這樣,s3指向了堆中創建的這個"xyz"字符串對象。s4則指向了堆中創建的另一個"xyz"字符串對象。s3 、s4是兩個指向不同對象的引用,結果當然是false。

 

    public static void main(String[] args) {
        String str1 = "hello ,String";
        String str2 = "hello ,String";
        System.out.println("str1 == str2: " + (str1 == str2));


        String str3 = new String("heihei,String");
        String str4 = new String("heihei,String");
        System.out.println("str3 == str4 :" + (str3 == str4));


        String str5 = "haha,String";
        String str6 = new String("haha,String");
        System.out.println("str5 == str6 : " + (str5 == str6 ));

    }

  運行結果:

str1 == str2: true
str3 == str4 :false
str5 == str6 : false

  

 

二、Intern的實現原理(JDK1.8)

先看一下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(); 

 

 

String#intern方法中看到,這個方法是一個 native 的方法,但注釋寫的非常明了。“

當調用 intern方法時,

如果池已經包含一個等於此String對象的字符串(用equals(oject)方法確定),則返回池中的字符串。

否則,將此String對象添加到池中,並返回此String對象的引用。“

三、JDK1.7的Intern的執行

來看一段代碼:

public static void main(String[] args) { String s1 = new String("1"); String s3 = s1.intern(); String s2 = "1"; System.out.println(s1 == s2); System.out.println(s2 == s3); System.out.println(s1 == s3); //第二種情況 String s3 = new String("1") + new String("1"); String s5 = s3.intern(); String s4 = "11"; System.out.println(s3 == s4); } 

 

 

首先分析一下這個代碼:

依據上面的說法,String s = new String("1");這行代碼做的事情是;JVM首先在字符串池中查找有沒有"1"這個字符串對象,可知在字符串常量池中沒有“1”,則首先在字符串池中創建一個"1"字符串對象,然后再在堆中創建一個"1"字符串對象,然后將堆中這個"1"字符串對象的地址返回賦給s1引用,這樣,s1指向了堆中創建的這個"1"字符串對象;String s3 = s1.intern();先來看一下JDk1.7及其之后的情況,首先會去字符串常量池中檢查池是否包含一個等於“1”的字符串,因為在字符串常量池中已經有字符串“1”對象,所以此時s3指向的是方法區中的對象;String s2 = "1";這句代碼首先會去字符串的常量池中查否包含一個等於“1”的字符串,因為字符串常量池中已經存在方法區中了,所以s2指向的是字符串常量池中的對象。

答案就非常好理解了s1 == s2為false ,s2 == s3為true ,s1 == s3為false;

下面分析一下下面的第二種情況:

String s3 = new String("1") + new String("1");首先分析一下這句代碼的執行,第一個new String("1")會在字符串常量池和堆內存中創建兩個對象,第二個new String("1")此時因為在方法區中已經存在了字符串“1”對象,所以不會再方法區中創建,但是仍然會在堆內存中創建字符串“1”對象。但是new String("1") + new String("1")會在堆中創建一個對象(假設名稱為obj),對象的內容為"11",s3指向新創建的obj對象;s3.intern();這行代碼在JDK1.6及其以前的版本中的執行情況是,首先會去字符串常量池中尋找是否已經包含一個等於此String對象(“11”)的字符串(用equals(oject)方法確定),因為在常量池中沒有,將 s3中的“11”字符串放入 String 常量池中 ,在常量池中生成一個 “11” 的對象 ;關鍵點是 jdk7常量池中不需要再存儲一份對象了,可以直接存儲堆中的引用。這份引用指向 s3 引用的對象。 也就是說引用地址是相同的 ;String s4 = "11";這句代碼會在字符串常量池中找"11"對象,因為前面intern()方法執行過了,所以此時返回的是s3引用對象的的引用,也就是"11"對象的地址;所以此時s3 == s4為true

關於這一段代碼我這里有一個疑問,如果有看到這篇文章的小伙伴希望你能幫我解答一下:

@Test public void test4(){ String s3 = new String("1") + new String("1"); String s5 = s3.intern(); String s4 = "11"; System.out.println(s5 == s3); System.out.println(s5 == s4); System.out.println(s3 == s4); System.out.println("======================"); String s6 = new String("go") +new String("od"); String s7 = s6.intern(); String s8 = "good"; System.out.println(s6 == s7); System.out.println(s7 == s8); System.out.println(s6 == s8); } 

 

 

這段代碼的運行結果是:

false
true
false

關於為什么會出現這樣的結果,現在我是沒辦法解釋的通的;隨后我修改了字符串“1”,把字符串改為字符串“2”出現的結果是true、true、true;這就更加令我想不通是為什么呢?如果有小伙伴看到,希望能告訴我一下,非常的感謝!(hhriver0601@163.com)

四:幾種特殊的情況的代碼

先來看一下這種情況的代碼:

1:代碼中含有StringBuffer的

public void test5(){ // Create three strings in three different ways. String s1 = "Hello"; String s2 = new StringBuffer("He").append("llo").toString(); String s3 = s2.intern(); // Determine which strings are equivalent using the == // operator System.out.println("s1 == s2? " + (s1 == s2)); System.out.println("s1 == s3? " + (s1 == s3)); System.out.println(); } 

 

 

先分析一下代碼:

String s1 = "Hello";和上面分析的一樣,在字符串常量池中創建“Hello”對象,然后返回字符串常量池中“Hello”對象的引用。String s2 = new StringBuffer("He").append("llo").toString();這句代碼會在字符串常量池中創建“He”對象和“llo”對象,(其實像“a”+"b"這樣的代碼,在編譯階段就會自動的有優化為StringBuilder.append()方法)然后在堆內存中會創建"He"對象,和內容為"Hello”的
對象,然后把這個對象的引用賦給s2;String s3 = s2.intern();去字符串常量池中找有沒有值為"Hello"的對象,因為此時字符串常量池中是有的,所以會把字符串常量池中“Hello”對象的引用賦給s3,所以此時的s1的值和s3的值是相等的;s1和s2的值是不相等的

2:new String(引用)的情形

public void test3(){ String m = "hello,world"; String n = "hello,world"; String u = new String(m); String v = new String("hello,world"); System.out.println(m == u); System.out.println(u == v); } 

 

 

分析String m = "hello,world" String n = "hello,world";這兩句代碼和前面的分析一樣,會在字符串常量池中創建一個對象“hello,world”,

m和n指向的是字符串常量池中的同一個對象;

String u = new String(m);會在堆中生成一個新的對象,但是內部的字符數組引用這m內部的字符數組;

String v = new String("hello,world");同樣會生成一個新的字符數組對象在堆里面,此時因為在常量池中已經存在了 "hello,world"對象,v指向的是堆里面的對象

3:“Java”字符串

public void test7(){ //發現原來是在JVM啟動的時候調用了一些方法,在常量池中已經生成了"java"字符串常量, String s2 = new String("ja") + new String("va"); String s3 = s2.intern(); String s4 = "java"; System.out.println(s2 == s3); System.out.println(s3 == s4); } 結果是: fasle true 

 

 

分析在String s2 = new String("ja") + new String("va");代碼中做的事情就是在字符串常量池中存儲了"ja"和"va"對象,在堆里面存儲的是"java"對象,String s3 = s2.intern();在字符串常量池中尋找有沒有“java”對象,由於JVM的 特殊性在JVM啟動的時候調用了一些方法,在常量池中已經生成了“java”字符串常量,所以s3直接返回字符串常量池中已經存在的“java”,s4也是同樣的道理,所以結果就非常的明顯了

參考的部分文章

深入解析String#intern(美團技術團隊)

String:字符串常量池(SegmentFault)

JDK1.8版本java字符串常量池里存的是String對象還是引用?(CSDN)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM