具有String构造函数和intern函数的Java String Pool

问题描述

我最近了解了Java字符串池,并且有些事情我不太清楚。

使用赋值运算符时,如果字符串池中不存在新字符串,则会在其中创建一个新字符串。

String a = "foo"; // Creates a new string in the String Pool
String b = "foo"; // Refers to the already existing string in the String Pool

使用String构造函数时,我知道无论String Pool处于什么状态,都将在String Pool之外的堆中创建一个新字符串。

String c = new String("foo"); // Creates a new string in the heap

我读到某个地方,即使在使用构造函数时,也正在使用字符串池 。它将把字符串插入字符串池到堆中。

String d = new String("bar"); // Creates a new string in the String Pool and in the heap

我没有找到任何进一步的信息,但是我想知道那是真的。

如果确实如此,那么-为什么?为什么Java创建此重复字符串?对我来说,这似乎完全是多余的,因为java中的字符串是不可变的。

我想知道的另一件事是String类的.intern()函数如何工作:它只是返回指向字符串池中字符串的指针吗?

最后,在以下代码中:

String s = new String("Hello");
s = s.intern();

垃圾收集器会从堆中删除字符串池之外的字符串吗?

解决方法

你写了

String c = new String("foo"); // Creates a new string in the heap

我读到某个地方,即使在使用构造函数时,也正在使用字符串池。它 会将字符串插入“字符串池”和堆中。

这有些正确,但是您必须正确阅读代码。您的代码包含两个String实例。首先,您拥有字符串文字"foo",其结果为一个String实例,该实例将被插入到池中。然后,您将使用String调用String(String) constructor来显式创建一个新的new String(…)实例。由于显式创建的对象不能与创建之前存在的对象具有相同的标识,因此必须存在两个String实例。

为什么Java创建此重复字符串?对我来说,这似乎完全是多余的,因为java中的字符串是不可变的。

这样做是因为您是这样说的。从理论上讲,这种结构可以得到优化,从而跳过了您始终无法感知的中间步骤。但是,对于程序行为的第一个假设应该是它完全按照您编写的内容进行操作。

您可能会问为什么有一个允许这种无意义操作的构造函数。实际上,这已经被询问过了,this answer对此进行了解决。简而言之,这主要是历史性的设计错误,但是出于其他技术原因,该构造函数已在实践中使用;有些不再适用。不过,如果不破坏兼容性就无法删除它。

String s = new String("Hello");
s = s.intern();

垃圾收集器会从堆中删除字符串池之外的字符串吗?

由于intern()调用将评估为"Hello"创建的实例,并且与通过new String(…)创建的实例不同,因此后者在第二次分配后肯定无法访问到s。当然,这并不能说明垃圾收集器是否会仅允许允许回收字符串的内存。但是请记住,堆占用的大部分将是保存字符数据的数组,该字符数据将在两个字符串实例之间共享(除非您使用过时的JVM)。只要两个字符串中的任何一个都在使用中,该数组将仍然处于使用状态。最近的JVM甚至具有String Deduplication功能,可能导致JVM中其他内容相同的字符串使用此数组(以允许收集其以前使用的数组)。因此,数组的寿命是完全不可预测的。

,
问:我读过某个地方,即使在使用构造函数时,也正在使用字符串池。它将把字符串插入字符串池和堆中。 []我没有找到任何进一步的信息,但是我想知道那是真的。

这不是事实。用new创建的字符串不会放置在字符串池中...除非有明确调用intern()的字符串。

问:为什么Java创建此重复字符串?

因为JLS指定每个 new生成一个新对象。如果不这样做(IMO),那将是违反直觉的。

在这种情况下,使用new String(String)几乎总是一个坏主意,但这并不是使new表现不同的一个好理由。真正的答案是程序员应该学会不要写那个……除非在极少数情况下有必要这样做。


问:我想知道的另一件事是String类的intern()函数如何工作:它只是返回一个指向String Pool中字符串的指针吗?

intern方法始终返回指向字符串池中字符串的指针。该字符串可能不是您称为intern()的字符串。

实现字符串池的方式有很多。

  • 在原始方案中,实习生字符串保存在称为PermGen堆的特殊堆中。在该方案中,如果您要实习的字符串不在池中,则将在PermGen空间中分配一个新的字符串,然后intern方法将返回该字符串。

  • 在当前方案中,实习字符串保存在普通堆中,并且字符串池只是一个(私有)数据结构。当字符串在池中插入时,它只是简单地链接到数据结构中。不需要分配新的字符串。


问:垃圾收集器是否会从堆中删除字符串池之外的字符串?

对于所有 Java对象,无论它们是如何创建的,并且无论它们位于何处(JVM中的哪个“空间”或“堆”),规则都是相同的。

如果无法从正在运行的应用程序访问对象,则垃圾回收器可以删除该对象。

这并不意味着在GC的任何特定运行中都会将垃圾对象 进行垃圾收集。 (或者甚至在某些情况下……)

以上规则同样适用于与字符串文字相对应的String对象。如果有可能永远无法再次使用文字,那么它会 被垃圾回收。

通常不会发生这种情况。 JVM在与定义它的类相关联的私有数据结构中保留对每个字符串文字对象的隐藏引用。由于类通常在JVM的生存期内一直存在,因此它们的字符串文字对象仍然可以访问。 (这很有意义……因为应用程序可能需要使用它们。)

但是,如果使用动态创建的类加载器加载了一个类,并且该类加载器变得不可访问,则其所有类也将无法访问。因此,字符串文字对象实际上有可能变得不可访问。如果是这样,它可能会 被垃圾收集。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...