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

String的intern方法

一、 核心概念:字符串常量池 (String Pool)

为了提升性能和减少内存开销,JVM 在内存堆中开辟了一块特殊的区域,称为字符串常量池

  • 作用:缓存所有通过字面量(如"hello")创建的字符串对象,以及显式调用intern()方法的字符串。

  • 目的:实现具有相同值的字符串只有一个副本,从而实现字符串的复用,节省内存。

  • 位置

    • JDK 1.6及之前:常量池位于方法区(永久代 PermGen)

    • JDK 1.7及之后:常量池被移到了 Java 堆(Heap) 中。这个改动的好处是常量池中的字符串也能被垃圾回收器管理,避免了在永久代中可能发生的内存溢出(OutOfMemoryError)。


2. intern() 方法的作用

intern() 是一个本地方法(Native Method),它的行为非常明确:

当一个字符串调用 intern() 方法时:

  1. 检查常量池:JVM 会检查字符串常量池中是否已经存在一个与该字符串值相等(通过 equals(Object) 方法判断)的字符串对象。

  2. 存在则返回引用:如果存在,则直接返回常量池中那个字符串的引用。

  3. 不存在则添加并返回:如果不存在,JDK 版本不同,行为有差异

    • 在 JDK 1.6 及之前:会在常量池中创建一份新的副本,然后返回这个新副本的引用。

    • 在 JDK 1.7 及之后:不会复制整个字符串,而是在常量池中记录首次遇到的该字符串的引用,并返回这个引用。这意味着常量池中存储的实际上是堆中对象的引用。

简单来说:intern() 是一个主动将字符串放入常量池并获取其引用的操作。它的最终目的是保证任何内容相同的字符串,在常量池中有且只有一个引用。


3. 代码示例

通过几段代码来彻底理解它。

示例 1:字面变量会放入当常量池,但是new String()会创建新对象,重新开辟地址
String s1 = "hello"; // 字面量创建:编译器会确保"hello"被放入常量池
String s2 = new String("hello"); // new创建:在堆中创建一个新的String对象,内容也是"hello"
String s3 = s2.intern(); // 调用intern,尝试将s2代表的字符串放入常量池System.out.println(s1 == s2); // false -> s1在池中,s2在堆中,是两个不同的对象
System.out.println(s1 == s3); // true -> s3得到的是常量池中"hello"的引用,也就是s1的引用

图解(JDK 1.7+):

堆 (Heap)                   字符串常量池 (String Pool)
+------------------+        +---------------------------+
| String对象 (s2)  |        | 记录引用: ----> "hello" <----+----> (s1, s3)
|  value -> "hello"|        +---------------------------+
+------------------+          ^|                       |+-----------------------+(s2.intern() 返回了这个堆对象的引用给s3,并记录在池中)
  • 字面量 "hello" 在编译期就已进入常量池(s1)。

  • new String("hello") 会在堆中创建一个新对象(s2)。

  • s2.intern() 发现常量池中已有内容为"hello"的字符串(即s1),所以直接返回s1的引用给s3

示例 2:JDK 1.7+ 的优化行为
String s4 = new String("world"); // 此时常量池中已有"world"(因为字面量)
String s5 = new StringBuilder().append("ja").append("va").toString();
// s5的内容是"java",但它是一个new出来的新对象String s6 = s5.intern(); // 在JDK7+中,常量池会记录s5的引用并返回
String s7 = "java"; // 字面量创建,现在会直接返回常量池中记录的引用,即s5的引用System.out.println(s5 == s6); // true in JDK7+! 因为s6拿到的就是s5本身的引用
System.out.println(s5 == s7); // true in JDK7+! 因为s7拿到的也是s5的引用

在 JDK 1.7+ 中,s5.intern() 发现常量池中没有 "java",它没有创建副本,而是s5这个堆对象的引用登记到了常量池中。所以后续任何获取 "java" 的操作,得到的都是堆中s5这个对象的引用。

示例 3:动态拼接的字符串,运行时拼接,会在堆中创建一个新的String对象 。
String s8 = "he";
String s9 = "llo";
String s10 = s8 + s9; // 运行时拼接,会在堆中创建一个新的String对象 "hello"
String s11 = "hello"; // 字面量,来自常量池System.out.println(s10 == s11); // falseString s12 = s10.intern(); // 将s10代表的"hello"尝试放入常量池,但池中已存在(s11),返回s11的引用
System.out.println(s10 == s12); // false
System.out.println(s11 == s12); // true

4. intern() 的用途与陷阱

用途:
  1. 节省内存:这是最主要的目的。如果你的程序中有大量重复的字符串(例如从文件中读取的重复数据、网络协议中的重复命令字),使用 intern() 可以确保这些重复字符串在内存中只存在一份,极大地减少内存占用。这在处理大规模数据时非常有效。

陷阱与注意事项:
  1. 性能开销intern() 方法本身不是免费的。它需要检查常量池,这个过程可能涉及哈希计算和查找。如果对大量不同的字符串调用 intern(),反而会增加性能开销,并可能使常量池变得过大。

  2. JDK 版本差异:如前所述,JDK 1.6 和 JDK 1.7+ 的行为有重要区别(复制 vs. 记录引用),这在某些极端case下可能导致不同的结果。

  3. 不可控的常量池:在 JDK 1.6 中,常量池位于永久代,大小固定(通过 -XX:MaxPermSize 设置),如果intern()过多字符串,容易导致 java.lang.OutOfMemoryError: PermGen space。虽然在 JDK 1.7+ 移到了堆中,但滥用依然可能引起堆内存溢出(Java heap space)。

  4. intern() 的自动化:Java 编译器已经会对字面量字符串自动执行 intern(),所以通常我们不需要对直接用字面量创建的字符串再调用此方法。

5. 最佳实践与总结

  • 谨慎使用:不要盲目地对所有字符串调用 intern()。它应该被用作一种优化手段,而不是默认操作。

  • 适用场景:通常用于处理已知会有大量重复生命周期较长的字符串。例如,数据库中的地区名称、枚举值、XML/JSON解析中的重复键名等。

  • 优先使用字面量:直接使用字面量 String s = "abc" 是最佳方式,因为它由编译器自动处理并放入常量池。

  • 测试:如果你考虑使用 intern() 来优化内存,一定要进行充分的测试和性能剖析(Profiling),确保它确实带来了好处,而不是引入了新的问题。

总结

特性描述
方法签名public native String intern()
核心作用返回字符串在常量池中的唯一引用,实现字符串复用
JDK 1.6常量池在永久代;intern() 会创建副本放入池中
JDK 1.7+常量池在堆;intern() 会记录堆中对象的引用到池中
主要用途节省内存,避免大量重复字符串对象的存在
主要风险性能开销、可能引起内存溢出(尤其在JDK1.6)、需谨慎使用
http://www.dtcms.com/a/349163.html

相关文章:

  • 数据库服务优化设置
  • nano命令使用方法
  • 备考NCRE三级信息安全技术 --- L1 信息安全保障概述
  • 自编 C# 颜色命名和色彩显示,使用 DataGridView 展示颜色命名、RGB值
  • 推进数据成熟度旅程的 3 个步骤
  • 基于 MATLAB 的信号处理实战:滤波、傅里叶变换与频谱分析
  • 什么是IP代理
  • 智慧农业病虫害监测误报率↓82%!陌讯多模态融合算法实战解析
  • 基于微信小程序校园微店源码
  • 电力电子simulink练习10:反激Flyback电路搭建
  • [leetcode] - 不定长滑动窗口
  • 深度学习卷积神经网络项目实战
  • 电容触控:自电容 vs 互电容
  • Rust 登堂 生命周期(一)
  • 内网后渗透攻击--域控制器安全(1)
  • 控制启动过程
  • 【typenum】 25 去除无符号整数前导零的特性(private.rs片段)
  • 重塑招聘战场:AI得贤招聘官AI面试智能体6.3如何用“精准”重新定义人才筛选?
  • C++(String):
  • 2025 年 8 月 22 日科技前沿:技术突破与范式跃迁的交汇点
  • golang1 专栏导学
  • 算法题(190):食物链(带权并查集)
  • leetcode 162 寻找峰值
  • 1、vue2面试题--生命周期
  • Goang开源库之go-circuitbreaker
  • HTTP请求中的CGI请求与登录注册机制
  • AI大模型企业落地指南-笔记01
  • Data_Formats_GRIDGeoTIFFShapeFile
  • 数据产品(2)用户画像数据分析模型
  • 【计算机视觉】CaFormer