当前位置: 首页 > news >正文

Java缓存String(字符串常量池)、Integer (-128 到 127 )

对问题的解释

1. “字符串常量池存储的是string对象的直接引用,而不是直接存放的对象,是一张string table” 的含义

这句话可以从以下几个方面理解:


(1) 字符串常量池的存储内容
  • 直接引用:字符串常量池中存储的是指向实际 String 对象的引用(即内存地址),而不是直接存储 String 对象本身。
  • 不是直接存放对象:实际的 String 对象仍然存在于堆内存中,字符串常量池仅保存这些对象的引用(类似“索引表”)。

(2) “string table” 的作用
  • 结构:字符串常量池内部通常以哈希表(Hash Table) 的形式实现,称为 string table
  • 功能:通过哈希表快速查找是否存在与某个字符串内容相同的对象:
    • 当程序中出现字符串字面量(如 "hello")时,JVM 会检查 string table 中是否存在该字符串的引用。
    • 如果存在,则直接返回该引用;如果不存在,则创建新对象并将其引用存入 string table

(3) 示例说明
String s1 = "hello"; // 1. 检查常量池是否有"hello",没有则创建并存入
String s2 = "hello"; // 2. 直接返回常量池中的引用,s1和s2指向同一个对象
  • 内存分布
    • 常量池中存储的是 "hello"引用(如地址 0x1000)。
    • 实际的 "hello" 对象在堆内存中,内容包含字符数组 {'h', 'e', 'l', 'l', 'o'}

2. 与缓存的关系

字符串常量池本质上是一种缓存机制,其核心目的是复用已有对象,从而节省内存和提升性能:


(1) 缓存的实现原理
  • 避免重复创建对象:当多个相同字符串被使用时,通过常量池共享同一个对象,减少内存占用。
    String s3 = new String("world"); // 1. 在堆中创建新对象,不放入常量池
    String s4 = new String("world"); // 2. 再次创建新对象,导致内存浪费
    System.out.println(s3 == s4);    // false(两个不同对象)
    
  • 通过 intern() 手动缓存:若希望 new String() 创建的对象也进入常量池,需调用 intern() 方法:
    String s5 = new String("test").intern(); // 将对象加入常量池
    String s6 = "test";                      // 直接指向常量池中的对象
    System.out.println(s5 == s6);            // true(同一个对象)
    

(2) 缓存的优势
  • 节省内存:相同内容的字符串只占用一份内存空间。
  • 提升性能
    • 快速查找:通过哈希表(string table)快速判断字符串是否存在。
    • 避免垃圾回收压力:减少短命对象的频繁创建和销毁。

(3) 与 JVM 内存区域的关系
  • JDK 7 之前:字符串常量池位于方法区(Method Area)
  • JDK 7 及以后:字符串常量池移至堆内存(Heap),与普通对象存储在同一区域,但通过 string table 管理引用。

3. 总结
  • 字符串常量池的本质:通过存储对象的引用(而非对象本身)实现一种缓存机制,利用哈希表(string table)快速复用相同内容的字符串对象。
  • 与缓存的关系:它是一种内存优化策略,通过减少对象重复创建来节省内存和提升性能,类似于其他缓存机制(如 Integer 的缓存区间)。

常见疑问解答
  1. 为什么 new String("abc") 不进入常量池?

    • 因为 new 总是创建新对象,且不自动调用 intern()。若需缓存,需显式调用 intern()
  2. intern() 的作用是什么?

    • 将字符串对象加入常量池,确保相同内容的字符串共享同一引用。
  3. 字符串拼接是否会影响常量池?

    • 字符串拼接(如 "a" + "b")会生成新对象,但若结果是编译期可知的字面量,则会被自动加入常量池:
      String s = "a" + "b"; // 编译时合并为"ab",直接使用常量池中的对象
      

通过理解字符串常量池的设计,可以更好地优化代码中字符串的使用,避免内存浪费并提升性能。

例子解释

1. 缓存范围内的值(-128 到 127)
Integer a = 100;  // 自动装箱,调用 Integer.valueOf(100)
Integer b = 100;  // 再次调用 Integer.valueOf(100),返回缓存中的同一个对象
System.out.println(a == b);  // 输出 true(指向同一对象)
  • 原因
    ab 的值 100 在缓存范围内(-128 到 127)。
    Integer.valueOf(100) 会直接从缓存数组中获取已存在的 Integer 对象,因此 ab 指向同一个对象,== 比较返回 true

2. 超出缓存范围的值(如 128 或 -129)
Integer c = 128;  // 自动装箱,调用 Integer.valueOf(128)
Integer d = 128;  // 调用 Integer.valueOf(128),超出默认缓存范围
System.out.println(c == d);  // 输出 false(指向不同对象)
  • 原因
    128 超出默认缓存范围(-128 到 127)。
    Integer.valueOf(128)新建一个 Integer 对象,因此 cd 是两个不同的对象,== 比较返回 false

3. 使用 new 创建对象(绕过缓存)
Integer e = new Integer(100);  // 显式 new 对象,绕过缓存
Integer f = new Integer(100);  // 再次 new,创建新对象
System.out.println(e == f);    // 输出 false(即使值相同,也是不同对象)
  • 原因
    使用 new 关键字会直接创建新对象,完全绕过缓存机制。因此 ef 是不同的对象,== 返回 false

关键点总结

场景行为== 结果原因
值在缓存范围内(如 100)自动装箱调用 Integer.valueOf(),返回缓存中的同一个对象。trueab 指向同一对象。
值超出范围(如 128)自动装箱调用 Integer.valueOf(),但超出缓存范围,因此每次新建对象。falsecd 是不同的对象。
使用 new 创建对象显式调用构造函数,绕过缓存,每次创建新对象。falseef 是不同的对象。

补充说明

1. 如何比较值的大小?
  • 使用 equals() 方法
    System.out.println(c.equals(d));  // 输出 true(比较值,而非引用)
    System.out.println(e.equals(f));  // 输出 true(值相同)
    
  • 避免 == 比较
    == 比较的是对象的引用(内存地址),而 equals() 比较的是对象的值。
2. 缓存范围的调整
  • 通过 JVM 参数调整
    可以通过 -XX:AutoBoxCacheMax=200 或设置系统属性 java.lang.Integer.IntegerCache.high=200 来扩展缓存范围。
    示例
    // 假设将缓存范围调整为 -128 到 200
    Integer g = 150;  // 自动装箱,命中缓存
    Integer h = 150;  // 返回缓存中的对象
    System.out.println(g == h);  // 输出 true
    

完整代码示例

public class IntegerCacheExample {
    public static void main(String[] args) {
        // 场景1:值在缓存范围内
        Integer a = 100;
        Integer b = 100;
        System.out.println("a == b: " + (a == b));  // true
        
        // 场景2:值超出缓存范围
        Integer c = 128;
        Integer d = 128;
        System.out.println("c == d: " + (c == d));  // false
        
        // 场景3:使用 new 创建对象
        Integer e = new Integer(100);
        Integer f = new Integer(100);
        System.out.println("e == f: " + (e == f));  // false
        
        // 使用 equals 比较值
        System.out.println("c.equals(d): " + c.equals(d));  // true
        System.out.println("e.equals(f): " + e.equals(f));  // true
    }
}

输出结果

a == b: true
c == d: false
e == f: false
c.equals(d): true
e.equals(f): true

通过这个例子,可以清晰地看到 Integer 缓存机制的作用和不同场景下的行为差异。

相关文章:

  • Webpack 打包技术及逆向数据分析研究
  • 8、STL中的map和pair使用方法
  • How to develop Cangjie applications based on Jetbrains Fleet
  • MySQL 在 CentOS 7 上安装的步骤指南
  • 麒麟服务器操作系统PostgreSQL环境部署手册
  • Dubbo 服务发现
  • 【Linux】五种 IO 模型与非阻塞 IO
  • K8s的部署
  • 深度学习部署到小程序
  • [RN 实践有效]Expo+cross-env配置项目环境变量
  • Linux项目自动化构建工具 - make/Makefile 练习 进度条 倒计时
  • 深入解析 FID:深度学习生成模型评价指标
  • netty中黏包,半包
  • Axure大屏可视化原型模板及素材:数据可视化的高效解决方案
  • PowerBI数据建模基础操作1:数据关系(基数、双向筛选、常规关系、有限关系)与星型架构(维度表、事实表)
  • 位运算(基础算法)
  • MATLAB中wildcardPattern函数用法
  • Mastering SAP Analytics Cloud - Empower Your Business Users
  • QListView、QListWidget、QTableView和QTableWidget
  • 得物 一面
  • 呼和浩特65户业主被一房两卖,十年诉讼却难胜
  • “鱼米之乡”江苏兴化的产业哲学:以融合与创新重构价值链条
  • “五一”假期国内出游3.14亿人次,国内游客出游总花费1802.69亿元
  • 上海畅通“外转内”,外贸优品成“香饽饽”
  • 山大齐鲁医院回应护士论文现“男性确诊子宫肌瘤”:给予该护士记过处分、降级处理
  • 在海拔3980米驻守:“全国先进工作者”刘鹏与洛戈梁子警务站的9年