带有 String 构造函数和 intern 函数的 Java 字符串池

Pat*_*xis 0 java string garbage-collection string-interning string-pool

最近学习了Java String Pool,有一些不太明白的地方。

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

String a = "foo"; // Creates a new string in the String Pool
String b = "foo"; // Refers to the already existing string in the String Pool
Run Code Online (Sandbox Code Playgroud)

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

String c = new String("foo"); // Creates a new string in the heap
Run Code Online (Sandbox Code Playgroud)

我读的地方,使用构造函数,即使,字符串池中使用。它将字符串插入到字符串池堆中。

String d = new String("bar"); // Creates a new string in the String Pool and in the heap
Run Code Online (Sandbox Code Playgroud)

我没有找到关于此的任何进一步信息,但我想知道这是否属实。

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

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

最后,在以下代码中:

String s = new String("Hello");
s = s.intern();
Run Code Online (Sandbox Code Playgroud)

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

Hol*_*ger 5

你写了

String c = new String("foo"); // Creates a new string in the heap
Run Code Online (Sandbox Code Playgroud)

我在某处读到,即使在使用构造函数时,也在使用字符串池。它将字符串插入到字符串池和堆中。

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

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

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

您可能会问为什么有一个构造函数允许这种毫无意义的操作。事实上,之前已经问过这个问题这个答案解决了这个问题。简而言之,多半是历史设计错误,但由于其他技术原因,此构造函数已在实践中使用;有些不再适用。尽管如此,它不能在不破坏兼容性的情况下被删除。

String s = new String("Hello");
s = s.intern();
Run Code Online (Sandbox Code Playgroud)

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

由于intern()调用将评估为已创建的实例,"Hello"并且与通过创建的实例不同new String(…),后者在第二次分配给 后肯定无法访问s。当然,这并不是说垃圾收集器是否在允许的情况下回收字符串的内存。但请记住,大部分堆占用将是保存字符数据的数组,它将在两个字符串实例之间共享(除非您使用非常过时的 JVM)。只要两个字符串中的任何一个都在使用中,这个数组就会一直被使用。最近的 JVM 甚至有字符串重复数据删除可能导致 JVM 中相同内容的其他字符串使用此数组的功能(以允许收集它们以前使用的数组)。所以数组的生命周期是完全不可预测的。