【问题标题】:String Constant Pool and intern字符串常量池和实习生
【发布时间】:2015-10-29 14:18:57
【问题描述】:

最近几天一直在尝试理解字符串常量池和inter的概念,在阅读了很多文章后,我理解了其中的一部分,但仍然对一些事情感到困惑:-

1.String a = "abc" 这会在字符串常量池中创建一个对象 但是以下代码行是否在字符串常量池中创建了对象“xyz”? String b = ("xyz").toLowerCase()

2.

String c = "qwe"   
String d = c.substring(1)    
d.intern()   
String e = "we" 

是否应该在类加载期间将文字“we”添加到字符串常量池中,如果是,为什么d==e即使d没有指向字符串常量池也会导致true

【问题讨论】:

  • d==e 不会返回 true 除非您设置 d = d.intern()
  • @VinodMadyalkar d == etrue 因为它们都是实习值并指向同一个 String 对象,我只是在测试文件中进行了测试以确保
  • @VinodMadyalkar 我认为正在发生的事情是来自d = c.substring(1) 的“我们”字符串自动检查池并使用实习生“我们”
  • @VinodMadyalkar d==e 结果为真!!
  • @VinodMadyalkar 在 Java 的 Mac 版本和 compilejava.net 上测试(我相信在 Debian 上运行),它们都返回 true

标签: java string string-literals


【解决方案1】:

正在延迟加载字符串池。如果您在字符串文字之前自己调用 intern(),那么这就是将进入字符串池的字符串版本。如果您自己不调用 intern(),那么字符串文字将为我们填充字符串池。

令人惊讶的是,我们可以在常量池之前影响字符串池;如下面的代码 sn-ps 所示。


要了解为什么两个代码 sn-ps 具有不同的行为,重要的是要清楚

  1. the constant pool is not the same as the string pool。也就是说,常量池是存储在磁盘上的类文件的一部分,而字符串池是填充了字符串的运行时缓存。

  2. 并且根据 Java 语言规范 jls-3.10.5,引用字符串文字不会直接引用常量池;当且仅当字符串池中还没有值时,字符文字才会从常量池中填充字符串池。

也就是说,一个String对象从源文件到运行时的生命周期如下:

  1. 在编译时由编译器放入常量池,并存储在生成的类文件中(每个类文件有一个常量池)
  2. 常量池由 JVM 在类加载时加载
  3. 从常量池创建的字符串在运行时被添加到字符串池中,因为 intern 被调用(如果等效字符串不存在,如果已经存在字符串,则将使用字符串池中的字符串) JVM Spec 5.1 - The Run-Time Constant Pool
  4. intern 可以通过手动调用 intern() 显式发生,也可以通过引用诸如“abc”jls-3.10.5 之类的字符串文字隐式发生。

以下两个代码 sn-ps 之间的行为差​​异是由于在通过字符串字面量对 intern 的隐式调用发生之前显式调用 intern() 引起的。

为了清楚起见,这里是在 cmets 中讨论到这个答案的两种行为:

    String c = "qwe";   // string literal qwe goes into runtime cache
    String d = c.substring(1); // runtime string "we" is created
    d.intern();         // intern "we"; it has not been seen 
                        // yet so this version goes into the cache
    String e = "we";    // now we see the string literal, but
                        // a value is already in the cache and so 
                        // the same instance as d is returned 
                        // (see ref below)

    System.out.println( e == d );  // returns true

下面是我们在使用字符串文字后实习时发生的情况:

    String c = "qwe";   // string literal qwe goes into runtime cache
    String d = c.substring(1); // runtime string "we" is created
    String e = "we";    // now we see the string literal, this time
                        // a value is NOT already in the cache and so 
                        // the string literal creates an object and
                        // places it into the cache
    d.intern();         // has no effect - a value already exists
                        // in the cache, and so it will return e

    System.out.println( e == d );  // returns false
    System.out.println( e == d.intern() );  // returns true
    System.out.println( e == d );  // still returns false

下面是 JLS 的关键部分,说明 intern 被隐式调用用于字符串文字。

此外,字符串字面量总是引用 String 类的同一个实例。这是因为字符串字面量 - 或者更一般地说,作为常量表达式值的字符串(第 15.28 节) - 是“内部的”,以便使用 String.intern 方法共享唯一实例。

JVM 规范涵盖了从类文件加载的常量池的运行时表示的详细信息,并与实习生交互。

如果先前已在包含与 CONSTANT_String_info 结构给出的相同的 Unicode 代码点序列的类 String 的实例上调用方法 String.intern,则字符串字面量推导的结果是对同一实例的引用类字符串。 .

【讨论】:

  • @ArijitDasgupta 因为在这种情况下,字符串文字是在实习之前加载的,因此使用的是那个版本。实习不会返回并更改调用 .substring(1)(又名变量 d)的结果。它只是决定是否缓存该值,这完全取决于它是否已经看到该值。
  • 1) 可以;这真的取决于 JVM 的实现。在某个阶段,Sun 付出了很多努力,试图让 Java 对 GUI 用户显得活泼,也就是说,他们尽可能地让类加载变得懒惰,以加快JVM 启动时间。我也相信有一个论点,如果字符串文字永远不会被使用,为什么要消耗内存。
  • 2) 它不会更改 java 地址,因为该字符串尚未在缓存中返回 'this'(也就是 d 中已有的引用)
  • @ArijitDasgupta 基本上是的。引擎盖下可能有一些变化,因为根据 Java 的版本,实习生池已存储在不同的位置。所以物理地址可能不同,但Java级别的地址会保持不变。
  • @ArijitDasgupta 在您的问题的答案明确之前,我不想弄脏水;但是现在为了完整起见,我必须强调 .intern() 一直很慢,并且通常不被鼓励。 Java 8 中的新机制是该语言的发展方向,所以如果你真的需要字符串重复数据删除(大多数人都这样做),那么我强烈建议你阅读让 JVM 为你做这件事。
猜你喜欢
  • 2019-01-03
  • 2013-10-03
  • 1970-01-01
  • 2013-08-11
  • 1970-01-01
  • 2014-06-08
  • 2012-12-18
  • 2021-02-10
  • 2014-09-14
相关资源
最近更新 更多