Java 之详解String.intern()方法(十一)

ModelEngine·创作计划征文活动 10w+人浏览 579人参与

十一、详解String.intern()方法

“哥,你发给我的那篇文章我看了,结果直接把我给看得不想学 Java 了!”浩妹气冲冲地说。

“哪一篇啊?”看着浩妹面色沉重,我关心地问到。

“就是美团技术团队深入解析 String.intern() 那篇啊!”浩妹回答。

“哦,我想起来了,不挺好一篇文章嘛,深入浅出,精品中的精品,看完后你应该对 String 的 intern 方法彻底理解了才对呀。”

“好是好,但我就是看不懂!”浩妹委屈地说,“哥,还是你亲自给我讲讲吧?”

“好吧,上次学的字符串常量池你都搞清楚了吧?”

“嗯。”浩妹微微的点了点头。

要理解美团技术团队的这篇文章,只要记住一下几点:

  1. 使用双引号声明的字符串对象会保存在字符串常量池中

  2. 使用new关键字创建的字符串对象会先从字符串常量池中找,如果没有找到,就在常量池中创建一个,然后再在堆中创建字符串对象;如果找到了,就直接在堆中创建字符串对象。

  3. 针对没有使用双引号声明的字符串对象来说,如下代码中的 s 那样:

    String str = new String("浩妹") + new String("好美");

    如果想把 s 的内容也放入字符串常量池的话,可以调用 intern() 方法来完成。

    不过,需要注意的是,Java 7 的时候,字符串常量池从永久代中移动到了堆中,此时永久代还没有完全被移除。Java 8 的时候,永久代被彻底移除。

    这个变化也直接影响了 String.intern() 方法在执行时的策略,Java 7 之前,执行 String.intern() 方法的时候,不管对象在堆中是否已经创建,字符串常量池中仍然会创建一个内容完全相同的新对象; Java 7 之后呢,由于字符串常量池放在了堆中,执行 String.intern() 方法的时候,如果对象在堆中已经创建了,字符串常量池中就不需要再创建新的对象了,而是直接保存堆中对象的引用,也就节省了一部分的内存空间。

看个例子

String s1 = new String("浩妹好美");
String s2 = s1.intern();
System.out.println(s1 == s2);

解释一下:

  • 第一行代码,字符串常量池中会先创建一个"浩妹好美"的对象,然后在堆中在创建一个"浩妹好美"的对象,s1引用的就是堆中的对象

  • 第二行代码,对s1执行intern()方法,该方法会从字符串常量池中查找"浩妹好美"这个字符串是否存在,此时是存在的,所有s2引用的是字符串常量池中的对象

这也意味着s1和s2的引用的地址是不同的,一个来自堆中,一个来自字符串常量池中,所以结果为 false

再来看一个例子

String s1 = new String("浩妹") + new String("好美");
String s2 = s1.intern();
System.out.println(s1 == s2);

这串代码就有所不同了

  • 第一行代码,会在字符串常量池中创建两个对象,一个是"浩妹",一个是"好美",然后在堆中会创建两个匿名对象"浩妹"和"好美",最后还有一个"浩妹好美"的对象(稍后会解释),s1引用的是堆中的"浩妹好美"这个对象

  • 第二行代码,对 s1 执行 intern() 方法,该方法会从字符串常量池中查找“二哥三妹”这个对象是否存在,此时不存在的,但堆中已经存在了,所以字符串常量池中保存的是堆中这个“二哥三妹”对象的引用,也就是说,s2 和 s1 的引用地址是相同的,所以输出的结果为 true。

在细节一点

  1. 创建 "浩妹" 字符串对象,存储在字符串常量池中。

  2. 创建 "好美" 字符串对象,存储在字符串常量池中。

  3. 执行 new String("浩妹"),在堆上创建一个字符串对象,内容为 "浩妹"。

  4. 执行 new String("好美"),在堆上创建一个字符串对象,内容为 "好美"。

  5. 执行 new String("浩妹") + new String("好美"),会创建一个 StringBuilder 对象,并将 "浩妹" 和 "好美" 追加到其中,然后调用 StringBuilder 对象的 toString() 方法,将其转换为一个新的字符串对象,内容为 "浩妹好美"。这个新的字符串对象存储在堆上。

也就是说,当编译器遇到 + 号这个操作符的时候,会将 new String("浩妹") + new String("好美") 这行代码编译为以下代码:

new StringBuilder().append("浩妹").append("好美").toString();

实际执行过程如下:

  • 创建一个 StringBuilder 对象。

  • 在 StringBuilder 对象上调用 append("浩妹"),将 "浩妹" 追加到 StringBuilder 中。

  • 在 StringBuilder 对象上调用 append("好美"),将 "好美" 追加到 StringBuilder 中。

  • 在 StringBuilder 对象上调用 toString() 方法,将 StringBuilder 转换为一个新的字符串对象,内容为 "浩妹好美"。

不过需要注意的是,尽管 intern 可以确保所有具有相同内容的字符串共享相同的内存空间,但也不要烂用 intern,因为任何的缓存池都是有大小限制的,不能无缘无故就占用了相对稀缺的缓存空间,导致其他字符串没有坑位可占。

另外,字符串常量池本质上是一个固定大小的 StringTable,如果放进去的字符串过多,就会造成严重的哈希冲突,从而导致链表变长,链表变长也就意味着字符串常量池的性能会大幅下降,因为要一个一个找是需要花费时间的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值