前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java String 的 常量池 和 intern 方法 简析

Java String 的 常量池 和 intern 方法 简析

作者头像
卢衍飞
发布2023-02-16 14:57:24
2170
发布2023-02-16 14:57:24
举报
文章被收录于专栏:技术分享交流技术分享交流

字符串常量池和 intern 方法 先举个例子,我们创建 10000 个相同的 String,并且不使用常量池

String[] list = new String[10000]; for (int i = 0; i < 10000; i++) {

代码语言:javascript
复制
list[i] = new String(new char[]{'a','b','c'});

} 结果是:每个 String 都用新开的对象,占用大量内存

现在我们这样创建,增加一个 map ,key 和 value 内容一样,每次取的时候,先检查一下 map 里面有没有,有就取 map 里面的,没有就放进去。即,创建 10000 个 String,使用自建常量池。

String[] list = new String[10000]; Map<String, String> pool = new HashMap<>(); for (int i = 0; i < 10000; i++) {

代码语言:javascript
复制
String s = new String(new char[]{'a','b','c'});
pool.putIfAbsent(s, s); // 等价于 if (pool.get(s) == null) pool.put(s, s);
list[i] = pool.get(s);

} 结果很明显,数组中每一个元素都指向堆中的同一个元素,其他新创建的 String 都会在下一次 GC 被清空。

其实,这个 map 就是字符串常量池。不过,JVM 把这个功能用 C++重新 实现了,存放在堆区。

那常量池这么好,要怎么使用 JVM 里面的常量池呢?

用双引号创建的 String ,自动使用常量池,比如 String a = "test"; 使用 String 的 intern 方法,使用常量池,比如 String s = new String(new char[]{'a','b','c'}); String intern = s.intern(); // 类似于上面的pool.putIfAbsent(s, s) 和 pool.get(s) 关于 intern 方法,JDK 文档这样写:当调用 intern 方法时,如果常量池(内置在 JVM 中的)中已经包含相同的字符串,则返回池中的字符串。否则,将此 String 对象添加到池中,并返回对该 String 对象的引用。

我们再用 intern 写一个存 10000 个字符串的代码,使用 JVM 常量池,结果和例子中的第二个代码一致

String[] list = new String[10000]; for (int i = 0; i < 10000; i++) {

代码语言:javascript
复制
list[i] = new String(new char[]{'a','b','c'}).intern();

} 或者使用双引号创建字符串,自动使用 JVM 常量池,结果和例子中的上面的代码一致

String[] list = new String[10000]; for (int i = 0; i < 10000; i++) {

代码语言:javascript
复制
list[i] = "abc";

} intern 方法的小特性 其实 intern 的使用并不复杂,上面的例子已经讲得非常清晰。 不过,如果你要研究茴香豆的茴字有几种写法的话,那么这里有一个细节要注意:

当堆区先创建了一个 String,并使用常量池,如果常量池中没有,常量池会直接把这个 刚刚在堆区创建的那个 String 作为 value 这个细节有点拗口,不过也非常好理解,我们搬回上面例子中的自建常量池:

String[] list = new String[10000]; Map<String, String> pool = new HashMap<>(); for (int i = 0; i < 10000; i++) {

代码语言:javascript
复制
String s = new String(new char[]{'a','b','c'});
pool.putIfAbsent(s, s);
list[i] = pool.get(s);

} 这个特性的意思就是说,常量池里面的 pool.get(s) 总是会返回第一个加进去的 s ,而不是别的。如果你走一下上面的代码,就会发现这个非常正常。

我们再复习一下使用常量池的两个方法:

用双引号创建的 String ,自动使用常量池 使用 String 的 intern 方法,使用常量池 那么,现在,我们祭出网上流传很广的题目:

public static void main(String[] args) {

代码语言:javascript
复制
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);

String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);

} 你可以用我上面讲的内容试一下答案。

答案是输出 false true,我再讲一次原理:

第二行,先执行括号中的 “1” ,在堆区创建了一个 String,我假设他的地址为 100 ,由于是双引号创建,自动使用常量池,设置常量池中 “1” 的 value 为 堆区 100 号 第二行,new String(xxx),在堆区又创建了一个 String,假设地址为 101,暂时没有使用常量池 第三行,很显然,s.intern() 会返回 100,但是并没有用变量接住 (谁知道网上出题的那个人脑子怎么想的呢) 第四行,双引号创建,自动使用常量池,返回的地址是 100 所以 s2 是 101,s 是 100,答案已经很明显了 第七行,两个”1”结合,堆区创建了一个 String s3,假设地址为 102 第八行,s3.intern() ,使用常量池,但是常量池里面没有 “11”,所以设置常量池的 11 的 value 为 地址 102 第九行,双引号创建自动使用常量池,所以 s4 地址为 102 好,再来第二题

public static void main(String[] args) {

代码语言:javascript
复制
String s = new String("1");
String s2 = "1";
s.intern();
System.out.println(s == s2);

String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);

} 答案是 false, false,我就不再细讲了,你可以自己推导一遍。如果还是不懂再重新看下上面的文章。

不适合用 intern 方法的情况 由于 JVM 里面的 C++写的 的 HashMap 设计并不像 JDK 的 HashMap 这么科学(超过链表负载链表转红黑树)。所以,如果你有几千万个不同的 String 要使用 intern 丢进常量池的话,那么,查找起来会非常慢。而且常量池也会变得非常大,所以,不建议丢太多不同的 String 进常量池

那硬是要丢可不可以呢,也是可以的,你可以扩大 JVM 的 -XX:StringTableSize 参数(jdk8 中默认为 60013),这个参数类似 JDK HashMap 的 initialCapacity。 但是这样子会占用更多的内存和 CPU。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023 年 01 月,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档