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

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 缓存机制的作用和不同场景下的行为差异。


http://www.kler.cn/a/588424.html

相关文章:

  • 计算机基础:二进制基础12,十进制数转换为十六进制
  • 联想台式电脑启动项没有U盘
  • 【医学影像 AI】大型语言模型生成 ROP 患者信息材料的能力
  • 编程题《牛牛的链表删除》的python可以用非链表的方式
  • 某省政务信创案例:3阶段实施×5类工具链选型经验分享
  • Word 小黑第18套
  • 用DasViewer的时候3Dtiles 转osgb 可以直接指定目标坐标系吗?
  • 【c++】【智能指针】什么情况下不适合智能指针
  • C++之stack_queue扩展
  • 【VUE】day04-组件的生命周期、组件之间的数据共享、ref引用、购物车案例
  • Axure高级功能深度解析一一高效原型设计的利器
  • 怎样用Java实现快速排序与找到数组中第k小的值?
  • AI第一天 自我理解笔记--微调大模型
  • L1-093 猜帽子游戏
  • fpga系列 HDL:ModelSim 波形绘制tips
  • 【软件】免费的PDF全文翻译软件,能保留公式图表的样式
  • ThinkPad T480s更换开机BIOS图片的详细步骤
  • windows系统,pycharm运行.sh文件
  • 机器学习 [白板推导](N)[谱聚类、前馈神经网络]
  • mysql学习-删除数据(drop、truncate、delete)