当我们有字符串池时为什么要进行字符串重复数据删除

Why String Deduplication when we have String Pool

String De-duplication:

Strings consume a lot of memory in any application.Whenever the garbage collector visits String objects it takes note of the char arrays. It takes their hash value and stores it alongside with a weak reference to the array. As soon as it finds another String which has the same hash code it compares them char by char.If they match as well, one String will be modified and point to the char array of the second String. The first char array then is no longer referenced anymore and can be garbage collected.

字符串池:

All strings used by the java program are stored here. If two variables are initialized to the same string value. Two strings are not created in the memory, there will be only one copy stored in memory and both will point to the same memory location.

所以 java 已经通过检查字符串是否存在于字符串池中来注意不在堆中创建重复的字符串。那么字符串去重的目的是什么?

如果有如下代码

    String myString_1 = new String("Hello World");
    String myString_2 = new String("Hello World");

在内存中创建了两个相同的字符串。我想不出除此之外的任何场景,其中字符串重复数据删除是有用的。显然我一定遗漏了什么。我错过了什么?

提前致谢

字符串池应用于显式添加到其中或在应用程序中用作常量的字符串。它适用于在应用程序生命周期内动态创建的字符串。然而,字符串重复数据删除适用于所有字符串。

字符串去重享有间接的额外级别String:

  • 使用字符串池,您只能为两个相同的字符串返回相同的对象
  • 字符串重复数据删除让您有多个不同的 String 对象共享相同的 content.

这意味着消除创建时去重的限制:您的应用程序可以继续创建具有相同内容的新 String 对象,同时使用很少的额外内存,因为字符串的内容将被共享。这个过程可以在一个完全不相关的时间表上完成——例如,在后台,而您的应用程序不需要太多 CPU 资源。由于 String 对象的身份没有改变,重复数据删除可以完全隐藏在您的应用程序之外。

编译时间 vs 运行 时间

字符串池是指编译时已知的字符串常量

如果您碰巧在 运行 时间检索(或构造)相同的字符串一百万次,则字符串重复数据删除会对您有所帮助,例如从文件、HTTP 请求或任何其他方式读取它。

补充一下上面的答案,在较旧的 VM 上,字符串池不是垃圾收集(现在已经改变,但不要依赖它)。它包含在应用程序中用作常量的字符串,因此始终需要。如果您不断地将所有字符串放入字符串池中,您可能会很快 运行 内存不足。最重要的是,去重是一个相对昂贵的过程,如果你知道你只需要在很短的时间内使用字符串,并且你有足够的内存。

由于这些原因,字符串不会自动放入字符串池中。您必须通过调用 string.intern().

来明确执行此操作

I cannot think of any scenario other than this where string de-duplication is useful.

另一个(更多)常见的情况是使用 StringBuilders。在StringBuilderclass的toString()方法中,明明在内存中新建了一个实例:

public final class StringBuilder extends AbstractStringBuilder
                                 implements java.io.Serializable, CharSequence
{
    ...

    @Override
    public String toString() {
       // Create a copy, don't share the array
       return new String(value, 0, count);
    }

    ...

}

线程安全版本也是如此StringBuffer:

public final class StringBuffer extends AbstractStringBuilder
                                implements java.io.Serializable, CharSequence
{
   ...

   @Override
   public synchronized String toString() {
       if (toStringCache == null) {
           toStringCache = Arrays.copyOfRange(value, 0, count);
       }
       return new String(toStringCache, true);
   }

   ...
}

在严重依赖此的应用程序中,字符串重复数据删除可能会减少内存使用量。

来自文档:

"初始化一个新创建的字符串对象,使其代表 与参数相同的字符序列;换句话说, 新创建的字符串是参数字符串的副本。除非一个 需要原始的显式副本,使用此构造函数是 不必要,因为字符串是不可变的。"

所以我的感觉是,String class 中的这个构造函数通常不需要像上面使用的那样。我想提供构造函数仅仅是为了完整性,或者如果您不想共享该副本(现在有点不必要,请参考 here 我在说什么)但还有其他构造函数很有用,例如获取 String来自 char 数组的对象等等..