【问题标题】:Trying to understand Java String implementation试图理解 Java String 的实现
【发布时间】:2012-08-14 02:52:51
【问题描述】:

我正在查看 String 的 openjdk 实现以及每个实例的私有成员如下所示:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** The value is used for character storage. */
    private final char value[];

    /** The offset is the first index of the storage that is used. */
    private final int offset;

    /** The count is the number of characters in the String. */
    private final int count;

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    [...]
}

但我知道 Java 对字符串使用引用和池,以避免重复。我天真地期待一个 pimpl 成语,其中 String 实际上只是一个 impl 的引用。到目前为止我还没有看到。有人可以解释如果我放一个 String x,Java 将如何知道使用引用吗?我的一门课的成员?

附录:这可能是错误的,但如果我处于 32 位模式,我应该计算:4 个字节用于引用“value[]”,4 个字节用于偏移量,4 个字节用于计数,4 用于所有实例的哈希值类字符串?这意味着写“String x;”在我的一个班级中,我的班级的“权重”自动增加了至少 32 个字节(我可能在这里错了)。

【问题讨论】:

  • 字符串就是字符串。它是否被池化对字符串的性质没有影响,只对它的存储位置和可以围绕它进行的优化。
  • pimpl 成语?我的猜测是您来自 C++ 背景,并且您没有意识到 Java 的对象模型与 C++ 的不同。 Java 不仅仅是拼写错误的 C++,它是一种非常不同的语言。我强烈建议您阅读 Java Language Specification 以更详细地了解 Java 是什么样的。
  • 猜得好!我是一个 C++ 人。我对 Java 有一些经验,但我觉得这很令人不安。

标签: java string size primitive-types


【解决方案1】:

偏移/计数字段与 pooling/intern() 问题有些正交。当你有类似的东西时,偏移和计数就会出现:

String substring = myString.substring(5);

实现此方法的一种方法是:

  • myString.length() - 5 元素分配一个新的char[]
  • 将所有元素从索引索引 5 复制到 myString.length(),从 myString 复制到新的 char[]
  • substring 是用这个新的char[] 构造的
    • substring.charAt(i) 直接转至chars[i]
    • substring.length() 直接转至chars.length

如您所见,这种方法是 O(N)——其中 N 是新字符串的长度——并且需要两次分配:新字符串和新字符 []。因此,substring 通过重用原始 char[] 但带有偏移量来工作:

  • substring.offset = myString.offset + newOffset
  • substring.count = myString.count - newOffset
  • 使用myString.chars 作为substring 的字符数组
    • substring.charAt(i) 转到 chars[i+substring.offset]
    • substring.length() 转到 substring.count

请注意,我们不需要创建新的 char[],更重要的是,我们不需要将旧 char[] 中的 chars 复制到新的 char[] 中(因为没有新的)。所以这个操作只是 O(1) 并且只需要一个分配,即新字符串的分配。

【讨论】:

  • So this operation is just O(N) and requires only one allocation, that of the new String. 这应该是O(1)。错误一年多未改正!大声笑
【解决方案2】:

Java 总是 使用对任何对象的引用。没有办法让它不使用引用。至于字符串池,这是由编译器为字符串文字实现的,并在运行时通过调用String.intern 来实现。很自然,String 的大多数实现都忽略了它是否正在处理常量池引用的实例。

【讨论】:

  • 字符串 a = "a";和字符串 b = new String("a");也使用两种不同的内存模型。
  • 弗兰克,你必须明白事情并没有那么简单。您的班级不是 String 实例的所有者。实例本身,更重要的是,支持char[],正在被共享,因此内存被重用。
  • @LouisWasserman 通常即使这样也不会结束计算,因为必须考虑内存对齐,这会将计数增加到最接近的 8 倍数。
  • String x; 引入了一个占用四个字节的空引用。目前还没有实际的String 值,所以这就是成本的全部范围。
  • @MarkoTopolnik 内存模型 = 池化与非池化(我想)
【解决方案3】:

Java 字符串是不可变的。这意味着实现可以对内部表示做很多事情,而不会破坏任何应用程序代码。

请注意,Java String.intern() 已在 Oracle 的 JDK 实现中被定义为本机。 本机代码可以访问对象的所有字段,并且可以在水下更改引用。因此,实现者所要做的就是将引用和偏移更改为字符串被实习的位置,瞧。当然,这破坏了类的不变性,因此这意味着 intern() 更新最好是线程安全的。

当您在新生成的字符串上调用intern() 时,您可以检查这些字段会发生什么。如果什么都没发生,则可能是引用本身包含内存位置。 Java 语言规范没有定义如何实现引用。

【讨论】:

    【解决方案4】:

    接受的答案和其他答案已过时。在 Java 7 更新 6 之后,Java 中的字符串不再使用偏移量并且未针对子字符串优化进行调整。相反,每个子字符串都会创建一个新的字符串副本。

    如果您想使用原始字符串实现,则必须使用 CharSequence。

    欲了解更多信息:https://jaxenter.com/the-state-of-string-in-java-107508.html

    【讨论】:

      猜你喜欢
      • 2011-07-10
      • 1970-01-01
      • 2019-08-19
      • 2018-03-02
      • 2013-08-09
      • 1970-01-01
      • 1970-01-01
      • 2011-07-08
      • 1970-01-01
      相关资源
      最近更新 更多