參考筆記:
Java String 類深度解析:內存模型、常量池與核心機制_java stringx、-CSDN博客
解析java中String的內存原理_string s1 = new string("ab");內存分析-CSDN博客
目錄
1.String初識
2.字符串字面量
3.內存原理圖
4. 示例驗證
4.1 字面量直接賦值
4.2 new方式賦值
4.3 new和直接賦值混合
4.4 字符串拼接
4.4.1 +號兩邊至少有一個變量
4.4.2 +號兩邊都是字面量
4.5 intern()方法
4.5.1 示例代碼1
4.5.2 示例代碼2
1.String初識
(1)Java 的 String 是引用數據類型
(2)因為字符串使用比較頻繁,所以 Java 專門為字符串準備了一個字符串常量池。 Java 8 之前字符串常量池在方法區中,Java 8 之后在堆中,本文講的是 Java 8 之前
(3)放在字符串常量池中的好處就是省去了對象的創建過程,從而提高程序的執行效率。常量池是一種緩存技術,緩存技術是提高程序執行效率的重要手段
(4)字符串一旦創建是不可變的(String 源碼有一個屬性:private final byte[] value)
?示例:String s = "hello"
其中 "hello" 存儲在字符串常量池中,字符串常量池中的 "hello" 不可變,不能變成 "hello123"。而 s 仍然可以指向其他的字符串對象,例如 s = "xyz"
2.字符串字面量
字符串字面量:我們自己給出的字符串,也可以稱作字符串常量。如 "123","abc"
判斷方法:簡單來說就是在程序中的任何位置,只要出現帶上英文雙引號的就可以算是字符串字面量
字符串常量池規則
字符串字面量一旦出現,會先去方法區里的字符串常量池找有沒有該字符串常量。
(1)如果有,則直接返回字符串常量池中存放該字符串的空間的地址
(2)如果沒有,則在字符串常量池里面開辟一塊空間用來存放該字符串常量,并返回空間地址
示例代碼
public class demo {public static void main(String[] args) {String s1 = "123";String s2 = new String("456");//"456"String s3 = "12";String s4 = "k";String s5 = s3+s4;//"12K"String s6 = s3+"馬";//"12馬"String s7 = "s"+"abc";//"sabc"}
}
經過上述代碼,字符串常量池中有字符串:"123","456","12","k","馬","s","abc","sabc"
這些都是字符串字面量,但是字符串常量池中不會有 "12k"?、"12馬"(后面會作解釋)
3.內存原理圖
4. 示例驗證
4.1 字面量直接賦值
注:s1 == s2 比較的是 s1 與 s2 的引用地址是否相同, s1.eauals(s2) 比較的是 s1與 s2 的內容是否相同
示例代碼?
public class demo {public static void main(String[] args){String s1="12";//字符串字面量12會先在方法區中的字符串常量池中找,//發現沒有同內容的字符串常量,那么就開辟一個新的空間,存放12//然后再把這個地址賦值給s1String s2="12";//字符串字面量12會現在方法區中的字符串常量池中找,//發現已經存在了字符串常量12了,此時無需再區開辟空間//只需要把已經存在的字符串常量的地址賦值給s2就行了//s1與s2指向的是同一個字符串常量的地址,所以s1==s2,輸出trueSystem.out.println(s1==s2);}
}
示例代碼內存原理圖
4.2 new方式賦值
注:Java 開發中很少使用 new 的方式給 String 賦值,因為在堆中會產生不必要的內存分配,直接使用字面量賦值更高效
示例代碼
public class demo {public static void main(String[] args) {String s1 = new String("123");String s2 = new String("123");//只要有new就會在堆內存中開辟空間//字符串字面量在字符串常量池中開辟的空間的那個地址值會存放到開辟的堆內存中//s1,s2指向的都是自己堆內存中開辟的空間,并沒有直接指向字符串常量池的"123"的那個地址//因此s1與s2進行 == 比較,輸出為falseSystem.out.println(s1 == s2);//s1與s2內容相同,輸出為trueSystem.out.println(s1.equals(s2));}
}
示例代碼內存原理圖?
4.3 new和直接賦值混合
示例代碼
public class demo {public static void main(String[] args) {String s1=new String("123");String s2="12"+"3";//會在字符串常量池開辟"123","12","3"的空間,//"123"在字符串常量池中開辟的空間地址賦值到了s1中開辟的堆空間中,s1指向的是堆空間地址//"123"在字符串常量池中開辟的空間地址直接賦值給了s2//因此,s1與s2進行 == 比較,輸出為falseSystem.out.println(s1==s2);}
}
示例代碼內存原理圖
4.4 字符串拼接
4.4.1 +號兩邊至少有一個變量
如果 + 號兩邊至少有一個是變量,則用 + 拼接生成的新的字符串不會被放到字符串常量池中,只會存放到堆中
示例代碼
public class demo {public static void main(String[] args) {//字符串常量池中創建"123","456"String s1 = "123";String s2 = "456";//s3="123456"是拼接而來,所以"123456"不在字符串常量池中,存放在堆中String s3 = s1 + s2;//字符串常量池中創建"123456"//s4的引用是字符串常量池中存放"123456"的地址String s4 = "123456";//s3與s4的引用不同,所以輸出為falseSystem.out.println(s3 == s4);//s3與s4的內容相同,輸出為trueSystem.out.println(s3.equals(s4));}
}
4.4.2 +號兩邊都是字面量
如果 + 號兩邊都是字符串字面量(常量),編譯器會進行自動優化。在編譯階段進行拼接。 + 號兩邊的字符串字面量、拼接后的新字符串都會被放到字符串常量池中,返回的引用也是來自字符串常量池
示例代碼
public class demo {public static void main(String[] args) {//字符串常量池中創建"123"、"456"、"123456"//返回字符串常量池中存放"123456"的地址String s1 = "123"+"456";//字符串常量池中已存在"456"//返回字符串常量池中存放"456"的地址String s2 = "456";//s2與"456"的引用相同,所以輸出為trueSystem.out.println(s2 == "456");//字符串常量池中已存在"123456"//返回字符串常量池中存放"123456"的地址String s3 = "123456";//s1與s3的引用相同,所以輸出為trueSystem.out.println(s1 == s3);}
}
示例代碼內存原理圖?
4.5 intern()方法
?intern() 檢查當前該字符串字面量是否已經存放于字符串常量池中
(1)存在:直接返回字符串常量池中存放該字符串字面量的空間地址
(2)不存在:將新的字符串字面量添加到常量池中,并返回引用
4.5.1 示例代碼1
示例代碼
public class demo {public static void main(String[] args) {String s1 = new String("123");//在字符串常量池開辟"123"的空間//"123"在字符串常量池中開辟的空間地址賦值到了s1中開辟的堆空間中,s1指向的是堆空間地址//字符串常量池中已有"123",調用intern()返回其在字符串常量池中的引用地址String s2 = new String("123").intern();//字符串常量池中已有"123",返回其在字符串常量池中的引用地址String s3 = "123";System.out.println(s1==s2);//falseSystem.out.println(s2==s3);//trueSystem.out.println(s1==s3);//false}
}
示例代碼內存原理圖?
4.5.2 示例代碼2
在 4.4.1 提到,如果 + 號兩邊至少有一個是變量,則用 + 拼接生成的新字符串不會被放到字符串常量池中,只會存放到堆中
這種場景下就可以用 intern() 方法來將 + 拼接生成的新字符串手動添加到字符串常量池中,并且返回的引用就來自字符串常量池
示例代碼?
public class demo {public static void main(String[] args) {String s1 = "hello";String s2 = "world";//拼接生成的"helloworld",存放在堆中String s3 = s1 + s2;//手動將拼接生成的"helloworld"添加到字符串常量池中,并返回引用String s4 = s3.intern();//字符串常量池中已有"helloworld",返回其在字符串常量池中的引用地址String s5 = "helloworld";//輸出trueSystem.out.println(s4==s5);}
}