最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java字符串常量池(String Constant Pool)原理、實現(xiàn)全解析

 更新時間:2025年12月19日 10:09:41   作者:Han.miracle  
本文詳細介紹了Java中的字符串常量池(String Constant Pool),包括其底層原理、存儲位置、創(chuàng)建機制、地址比較、intern方法以及常見問題,字符串常量池通過復(fù)用相同內(nèi)容的字符串對象,減少內(nèi)存開銷,提升性能,感興趣的朋友跟隨小編一起看看吧

字符串常量池(String Constant Pool)

字符串常量池是 Java 中用于優(yōu)化字符串內(nèi)存使用的核心機制,屬于 JVM 運行時數(shù)據(jù)區(qū)的重要組成部分。其本質(zhì)是一個字符串駐留池(String Intern Pool),核心目標(biāo)是復(fù)用相同內(nèi)容的字符串對象,減少內(nèi)存開銷,提升性能。下面從底層原理、JVM 存儲位置、創(chuàng)建機制、地址比較、intern 方法、常見問題等維度,全面解析字符串常量池的所有內(nèi)容。

一、字符串常量池的底層本質(zhì)與設(shè)計初衷

1. 設(shè)計初衷:解決字符串的內(nèi)存浪費問題

字符串是 Java 中最常用的對象之一,若每次創(chuàng)建相同內(nèi)容的字符串都新建對象,會導(dǎo)致大量重復(fù)對象占用內(nèi)存。例如:

// 若沒有常量池,會創(chuàng)建1000個內(nèi)容為"hello"的字符串對象
for (int i = 0; i < 1000; i++) {
    String s = "hello";
}

字符串常量池通過 **“駐留(Intern)” 機制 **,讓相同內(nèi)容的字符串僅保留一份副本,所有指向該內(nèi)容的字符串引用都指向這個副本,從而節(jié)省內(nèi)存。

2. 本質(zhì):一個哈希表(HashTable)

字符串常量池的底層實現(xiàn)是一個固定大小的哈希表(JDK1.7+),默認容量為 1009(可通過 JVM 參數(shù)-XX:StringTableSize調(diào)整,JDK1.8 后默認是 60013)。

  • 哈希表的key:字符串的內(nèi)容(字符序列)。
  • 哈希表的value:字符串對象的引用(JDK1.7+)或字符串對象本身(JDK1.6 及以前)。
  • 哈希表的作用:快速查找是否存在相同內(nèi)容的字符串,時間復(fù)雜度接近 O (1)。

二、字符串常量池的存儲位置(JDK 版本差異)

字符串常量池的存儲位置在不同 JDK 版本中存在重大變化,這是理解其機制的關(guān)鍵:

JDK 版本存儲位置存儲內(nèi)容關(guān)鍵說明
JDK1.6 及以前方法區(qū)(永久代,PermGen)字符串對象本身(字符數(shù)組 + 屬性)永久代內(nèi)存空間有限,容易出現(xiàn)OutOfMemoryError: PermGen space。
JDK1.7+堆內(nèi)存(Heap)字符串對象的引用(指向堆中的字符串對象)① 常量池從永久代移到堆,解決永久代內(nèi)存限制;② 存儲的是引用,而非對象本身,更節(jié)省內(nèi)存。
JDK1.8+堆內(nèi)存(Heap)字符串對象的引用JDK1.8 移除永久代,方法區(qū)改為元空間(Metaspace),常量池仍在堆中。

關(guān)鍵差異示例(JDK1.6 vs JDK1.7+)

String s = new String("abc");
// JDK1.6:常量池在永久代,存儲"abc"對象;堆中存儲另一個"abc"對象,s指向堆對象。
// JDK1.7+:常量池在堆中,存儲堆中"abc"對象的引用;堆中有一個"abc"對象(字面量),s指向堆中新建的另一個"abc"對象。

三、字符串的創(chuàng)建方式與常量池的交互

字符串的創(chuàng)建方式直接決定了其與常量池的交互邏輯,也是判斷 “地址是否相同” 的核心依據(jù)。

場景 1:直接使用字符串字面量創(chuàng)建(String s = "abc")

這是最常見的方式,完全依賴常量池的駐留機制。

執(zhí)行流程(JDK1.7+):
  1. JVM 首先檢查字符串常量池的哈希表,是否存在鍵為 “abc” 的條目。
  2. 若不存在
    • 堆內(nèi)存中創(chuàng)建一個字符串對象(內(nèi)容為 “abc”)。
    • 將該對象的引用存入字符串常量池的哈希表中(key=“abc”,value = 對象引用)。
    • 將引用變量s指向堆中的這個字符串對象。
  3. 若存在
    • 直接從常量池的哈希表中取出 “abc” 對應(yīng)的對象引用。
    • 將引用變量s指向該引用對應(yīng)的堆對象。
代碼示例:
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2); // true(指向同一個堆對象)
System.out.println(s1.equals(s2)); // true(內(nèi)容相同)

場景 2:使用new String("內(nèi)容")創(chuàng)建

這種方式會創(chuàng)建至少一個、最多兩個字符串對象,是最容易產(chǎn)生內(nèi)存浪費的方式。

執(zhí)行流程(JDK1.7+):
  1. 處理字符串字面量"abc":JVM 檢查常量池,若沒有則在堆中創(chuàng)建 “abc” 對象,并將引用存入常量池(這是第一個對象)。
  2. 執(zhí)行new String("abc"):在堆內(nèi)存中新建一個獨立的字符串對象(這是第二個對象),該對象的字符數(shù)組與常量池中的 “abc” 對象共享(JDK1.7 + 優(yōu)化,字符數(shù)組存儲在堆中,復(fù)用字符數(shù)組以節(jié)省內(nèi)存)。
  3. 引用變量s指向第二步新建的堆對象。
代碼示例:
String s1 = "abc"; // 常量池存引用,s1指向堆中的"abc"對象
String s2 = new String("abc"); // 堆中新建對象,s2指向該對象
String s3 = new String("abc"); // 堆中再新建對象,s3指向該對象
System.out.println(s1 == s2); // false(指向不同堆對象)
System.out.println(s2 == s3); // false(指向不同堆對象)
System.out.println(s1.equals(s2)); // true(內(nèi)容相同)
System.out.println(s2.intern() == s1); // true(intern()返回常量池引用)
關(guān)鍵:new String("abc")創(chuàng)建的對象數(shù)量
  • 最少 1 個:若常量池中已存在 “abc” 的引用,則僅在堆中創(chuàng)建一個新對象。
  • 最多 2 個:若常量池中不存在 “abc” 的引用,則先在堆中創(chuàng)建 “abc” 對象(供常量池引用),再在堆中創(chuàng)建一個新對象(供s引用)。

場景 3:字符串拼接(編譯期常量拼接 vs 運行期拼接)

字符串拼接的結(jié)果是否指向常量池對象,取決于拼接的是常量還是變量。

子場景 3.1:編譯期常量拼接(字面量 /final常量拼接)

特征:拼接的所有部分都是編譯期就能確定值的常量(字符串字面量、final字符串常量、基本類型常量)。原理:Java 編譯器會對這種拼接進行編譯期優(yōu)化,直接將拼接結(jié)果作為一個字符串字面量存入常量池,運行時不再執(zhí)行拼接操作。

代碼示例:
// 示例1:字面量拼接
String s1 = "abc";
String s2 = "ab" + "c"; // 編譯期優(yōu)化為"abc"
String s3 = "a" + "b" + "c"; // 編譯期優(yōu)化為"abc"
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true
// 示例2:final常量拼接
final String str1 = "ab"; // final常量,編譯期確定值
final int num = 123; // 基本類型常量
String s4 = str1 + "c"; // 編譯期優(yōu)化為"abc"
String s5 = "num" + num; // 編譯期優(yōu)化為"num123"
System.out.println(s1 == s4); // true
System.out.println("num123" == s5); // true
子場景 3.2:運行期拼接(變量拼接)

特征:拼接的部分包含運行期才能確定值的變量(非final字符串變量、對象引用等)。原理:這種拼接會在運行期通過StringBuilderappend()方法拼接,再調(diào)用toString()方法生成新的字符串對象,該對象是堆中的新對象,與常量池?zé)o關(guān)。

代碼示例:
String s1 = "abc";
String s2 = "ab";
String s3 = s2 + "c"; // 運行期拼接,底層用StringBuilder實現(xiàn)
String s4 = new String("ab") + new String("c"); // 運行期拼接,堆新對象
System.out.println(s1 == s3); // false(s3指向堆新對象)
System.out.println(s1 == s4); // false(s4指向堆新對象)
System.out.println(s3 == s4); // false(兩個不同的堆對象)

場景 4:使用String.intern()方法(手動駐留字符串)

intern()是 String 類的 native 方法(底層由 C/C++ 實現(xiàn)),作用是將字符串對象的內(nèi)容駐留到常量池,并返回常量池中的引用。這是手動控制字符串常量池的核心方法。

// 案例1:new String() + intern()
String s1 = "abc";
String s2 = new String("abc").intern();
System.out.println(s1 == s2); // true(s2指向常量池中的對象)
// 案例2:運行期拼接 + intern()
String s3 = "ab";
String s4 = s3 + "c"; // 堆中新對象
String s5 = s4.intern(); // 將s4的內(nèi)容"abc"加入常量池(已存在,返回引用)
System.out.println(s1 == s5); // true
System.out.println(s1 == s4); // false(s4還是堆中的對象)
        // 案例3:new String() + intern()
        String s1 = "abc";
        String s2 = new String("abc").intern();
        System.out.println(s1 == s2); // true(s2指向常量池中的對象)
        // 案例4:運行期拼接 + intern()
        String s3 = "ab";
        String s4 = (s3 + "c").intern(); // 修正:補充intern()的括號
        System.out.println(s4 == s1); // 結(jié)果:true
    }
}

原理

  1. 調(diào)用intern()時,JVM 檢查常量池:
    • 若常量池中已有該內(nèi)容的字符串,直接返回常量池中的引用。
    • 若沒有,將當(dāng)前字符串的內(nèi)容加入常量池(JDK1.7 + 是將堆中對象的引用存入常量池,JDK1.6 是復(fù)制對象到常量池),并返回常量池中的引用。結(jié)論intern()可以讓運行期創(chuàng)建的字符串指向常量池的地址,實現(xiàn) “內(nèi)容相同則地址相同”。
執(zhí)行流程(JDK1.7+):
  1. 調(diào)用s.intern()時,JVM 檢查字符串常量池的哈希表:
    • 若存在鍵為s內(nèi)容的條目:直接返回該條目對應(yīng)的引用。
    • 若不存在:將s引用存入常量池的哈希表(key=s 的內(nèi)容,value=s 的引用),并返回s的引用。
  2. JDK1.6 與 JDK1.7 + 的關(guān)鍵差異
    • JDK1.6:若常量池中不存在該字符串,會復(fù)制一份字符串對象到永久代的常量池,并返回永久代中對象的引用。
    • JDK1.7+:若常量池中不存在該字符串,會將堆中對象的引用存入常量池,而非復(fù)制對象,更節(jié)省內(nèi)存。
代碼示例(JDK1.7+):
// 示例1:new String() + intern()
String s1 = new String("abc"); // 堆對象1,常量池存"abc"的引用(指向堆中字面量對象)
String s2 = s1.intern(); // 常量池已有"abc"引用,返回該引用
System.out.println(s1 == s2); // false(s1指向堆對象1,s2指向常量池引用的對象)
// 示例2:運行期拼接 + intern()
String s3 = new String("ab") + new String("c"); // 堆對象2,內(nèi)容為"abc",常量池?zé)o該引用
String s4 = s3.intern(); // 將s3的引用存入常量池,返回s3的引用
System.out.println(s3 == s4); // true(s4指向s3的堆對象2)
System.out.println(s4 == "abc"); // true("abc"指向常量池中的s3引用)
// 示例3:字面量 + intern()
String s5 = "hello";
String s6 = s5.intern();
System.out.println(s5 == s6); // true(常量池已有引用,直接返回)
應(yīng)用場景:海量字符串去重

當(dāng)處理大量重復(fù)字符串(如日志、數(shù)據(jù)庫數(shù)據(jù))時,調(diào)用intern()方法可將重復(fù)字符串的引用指向常量池,大幅減少內(nèi)存占用。例如:

// 假設(shè)有100萬個內(nèi)容為"user_123"的字符串
String s = new String("user_123").intern();
// 后續(xù)所有"user_123"的字符串都調(diào)用intern(),則僅占用一份內(nèi)存

四、字符串常量池的地址比較:核心規(guī)則

Java 中判斷地址是否相同用==(比較引用),判斷內(nèi)容是否相同用equals()(比較字符序列)。結(jié)合常量池的機制,地址比較的核心規(guī)則如下:

字符串創(chuàng)建方式內(nèi)容相同時,地址是否相同(==底層原因
字符串字面量("abc"常量池自動復(fù)用相同內(nèi)容的引用。
new String("abc")每次new都創(chuàng)建堆新對象,與常量池引用無關(guān)。
編譯期常量拼接("ab"+"c"編譯器優(yōu)化為字面量,常量池復(fù)用引用。
運行期變量拼接(s2+"c"底層StringBuilder生成堆新對象。
new String("abc").intern()intern()返回常量池中的引用,與字面量引用相同。
運行期拼接 + intern()是(JDK1.7+)intern()將堆對象引用存入常量池,后續(xù)字面量指向該引用。

五、字符串常量池的常見問題與誤區(qū)

誤區(qū) 1:“字符串常量池存儲的是字符串對象”

糾正:JDK1.7 + 常量池存儲的是字符串對象的引用,而非對象本身;JDK1.6 及以前存儲的是對象本身。

  • 原因:JDK1.7 + 將常量池移到堆中,存儲引用可避免復(fù)制對象,節(jié)省內(nèi)存。

誤區(qū) 2:“String s = new String("abc")創(chuàng)建了一個對象”

糾正:最多創(chuàng)建兩個對象(常量池的字面量對象 + 堆的新對象),最少創(chuàng)建一個對象(常量池已有字面量,僅堆新對象)。

誤區(qū) 3:“intern()方法一定能節(jié)省內(nèi)存”

糾正intern()的哈希表是固定大小的,若大量調(diào)用intern()存儲不同內(nèi)容的字符串,會導(dǎo)致哈希表沖突,性能下降(從 O (1) 變?yōu)?O (n))。因此:

  • 適合大量重復(fù)字符串的場景(如去重)。
  • 不適合大量唯一字符串的場景(如 UUID),會浪費常量池空間,降低性能。

誤區(qū) 4:“字符串是不可變的,所以常量池中的字符串也不能變”

糾正:字符串的不可變是指字符數(shù)組被final修飾,且沒有提供修改字符的方法(如setCharAt()),但通過反射可以修改字符數(shù)組的內(nèi)容(不推薦)。例如:

String s = "abc";
// 通過反射修改字符數(shù)組
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
char[] value = (char[]) valueField.get(s);
value[0] = 'x';
System.out.println(s); // 輸出:xbc
System.out.println("abc"); // 輸出:xbc(常量池中的引用指向同一個對象,因此也被修改)

注意:這種操作會破壞常量池的駐留機制,導(dǎo)致所有指向該字符串的引用都被修改,屬于高危操作,禁止在生產(chǎn)環(huán)境使用。

誤區(qū) 5:“空字符串""也在常量池中”

糾正:空字符串""是一個合法的字符串字面量,會被駐留到常量池中。例如:

String s1 = "";
String s2 = "";
System.out.println(s1 == s2); // true(常量池復(fù)用引用)

六、字符串常量池的 JVM 參數(shù)調(diào)優(yōu)

1. 調(diào)整字符串常量池的哈希表大小

JVM 參數(shù):-XX:StringTableSize=<size>

  • JDK1.6:默認大小 1009,最大可設(shè)為 65535。
  • JDK1.7+:默認大小 60013(JDK1.8),可設(shè)為更大的質(zhì)數(shù)(如 1000003),減少哈希沖突。
  • 適用場景:當(dāng)系統(tǒng)中存在大量intern()調(diào)用時,增大哈希表大小可提升查找性能。

2. 開啟字符串去重(JDK1.8u20+)

JVM 參數(shù):-XX:+UseStringDeduplication

  • 作用:在 G1 垃圾收集器中,自動檢測堆中重復(fù)的字符串對象,讓它們共享同一個字符數(shù)組,進一步節(jié)省內(nèi)存。
  • 區(qū)別:與常量池的駐留機制不同,該參數(shù)是在堆中對字符串對象的字符數(shù)組去重,而常量池是對引用的駐留。

七、總結(jié)

字符串常量池是 Java 優(yōu)化字符串內(nèi)存的核心機制,其關(guān)鍵要點可歸納為:

  1. 存儲位置:JDK1.6 在永久代,JDK1.7 + 在堆中。
  2. 底層實現(xiàn):哈希表,存儲字符串內(nèi)容與引用的映射(JDK1.7+)。
  3. 核心機制:駐留(Intern),相同內(nèi)容的字符串僅保留一份引用。
  4. 創(chuàng)建方式:字面量創(chuàng)建復(fù)用常量池,new String()創(chuàng)建新對象,拼接分為編譯期和運行期。
  5. intern 方法:手動將字符串駐留到常量池,JDK1.7 + 存儲引用,JDK1.6 存儲對象。
  6. 地址比較==比較引用,equals()比較內(nèi)容,常量池中的字符串引用相同。

理解字符串常量池的機制,不僅能避免內(nèi)存浪費,還能在面試和生產(chǎn)環(huán)境中解決字符串相關(guān)的性能問題。

到此這篇關(guān)于Java字符串常量池(String Constant Pool)原理、實現(xiàn)全解析的文章就介紹到這了,更多相關(guān)java字符串常量池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 一文學(xué)會Java運算符的使用

    一文學(xué)會Java運算符的使用

    計算機的最基本用途之一就是執(zhí)行數(shù)學(xué)運算,作為一門計算機語言,Java也提供了一套豐富的運算符來操縱變量,本篇通過示例詳細講解了Java中不同運算符的使用,需要的朋友可以參考下
    2022-05-05
  • java  中ThreadLocal實例分析

    java 中ThreadLocal實例分析

    這篇文章主要介紹了java 中ThreadLocal實例分析的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • 淺談SpringMVC中的session用法及細節(jié)記錄

    淺談SpringMVC中的session用法及細節(jié)記錄

    下面小編就為大家?guī)硪黄獪\談SpringMVC中的session用法及細節(jié)記錄。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • java 流與 byte[] 的互轉(zhuǎn)操作

    java 流與 byte[] 的互轉(zhuǎn)操作

    這篇文章主要介紹了java 流與 byte[] 的互轉(zhuǎn)操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • SpringBoot使用@Async注解實現(xiàn)異步調(diào)用

    SpringBoot使用@Async注解實現(xiàn)異步調(diào)用

    這篇文章主要介紹了SpringBoot使用@Async注解實現(xiàn)異步調(diào)用,異步調(diào)用是相對于同步調(diào)用而言的,同步調(diào)用是指程序按預(yù)定順序一步步執(zhí)行,每一步必須等到上一步執(zhí)行完后才能執(zhí)行,異步調(diào)用則無需等待,程序執(zhí)行完即可執(zhí)行,可以減少程序執(zhí)行時間,需要的朋友可以參考下
    2023-10-10
  • java編程中自動拆箱與自動裝箱詳解

    java編程中自動拆箱與自動裝箱詳解

    這篇文章主要介紹了java編程中自動拆箱與自動裝箱詳解,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11
  • Java定時器@Scheduled注解的使用方法

    Java定時器@Scheduled注解的使用方法

    這篇文章主要給大家介紹了關(guān)于Java定時器@Scheduled注解的使用方法,在Java中使用@Scheduled注解可以方便地實現(xiàn)定時任務(wù)的功能,文中介紹的非常詳細,需要的朋友可以參考下
    2023-07-07
  • IDEA類和方法注釋模板設(shè)置(非常詳細)

    IDEA類和方法注釋模板設(shè)置(非常詳細)

    這篇文章主要介紹了IDEA類和方法注釋模板設(shè)置(非常詳細),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • Java替換字符串replace和replaceAll方法舉例詳解

    Java替換字符串replace和replaceAll方法舉例詳解

    這篇文章主要介紹了Java中替換字符串的幾種方法,包括String類的replace()、replaceAll()、replaceFirst()方法,以及StringBuilder和StringBuffer類的replace()方法,還提到了一些第三方庫,如Hutool,它們提供了更豐富的字符串處理功能,需要的朋友可以參考下
    2025-02-02
  • String實例化及static final修飾符實現(xiàn)方法解析

    String實例化及static final修飾符實現(xiàn)方法解析

    這篇文章主要介紹了String實例化及static final修飾符實現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-09-09

最新評論

无极县| 湄潭县| 青岛市| 达日县| 宁明县| 昭觉县| 泸水县| 东乌珠穆沁旗| 贵港市| 山西省| 永和县| 丰城市| 邓州市| 绥中县| 城市| 和平县| 南江县| 额济纳旗| 色达县| 明光市| 伊宁市| 天津市| 大庆市| 陈巴尔虎旗| 科技| 罗定市| 安泽县| 松阳县| 新宁县| 文山县| 乡宁县| 库尔勒市| 岑溪市| 高要市| 东方市| 靖西县| 孝义市| 盐山县| 班玛县| 临高县| 顺义区|