【问题标题】:Difference between compact strings and compressed strings in Java 9Java 9 中紧凑字符串和压缩字符串的区别
【发布时间】:2017-05-25 10:38:21
【问题描述】:

compact strings相对于JDK9中的压缩字符串有什么优势?

【问题讨论】:

  • 从来没有听说过 compressed 字符串(作为一个东西),但是有一个关于紧凑字符串的 JEP:Compact Strings
  • 现在看看吧 :D 但是,它总是帮助包括你读过的东西。

标签: java string java-9


【解决方案1】:

压缩字符串 (Java 6) 和紧凑字符串 (Java 9) 都具有相同的动机(字符串通常实际上是 Latin-1,因此浪费了一半的空间)和目标(使这些字符串变小),但实现不同很多。

压缩字符串

an interviewAleksey Shipilëv(负责实现 Java 9 功能)中谈到了压缩字符串:

UseCompressedStrings 功能相当保守:在区分char[]byte[] 的情况下,并尝试在String 构造上将char[] 压缩为byte[],它在char[] 上完成了大多数String 操作,这需要解压String. 因此,它只受益于一种特殊类型的工作负载,其中大多数字符串是可压缩的(因此压缩不会浪费),并且只对它们执行有限数量的已知String 操作(所以不需要拆包)。在许多工作负载中,启用-XX:+UseCompressedStrings 是一种悲观。

[...] UseCompressedStrings 实现基本上是一个可选功能,它在alt-rt.jar 中维护了一个完全不同的String 实现,它在提供VM 选项后被加载。可选功能更难测试,因为它们使要尝试的选项组合数量增加一倍。

压缩字符串

另一方面,在 Java 9 中,紧凑字符串完全集成到 JDK 源代码中。 String总是byte[] 支持,如果字符是 Latin-1,则使用一个字节,否则使用两个字节。大多数操作都会检查以查看是哪种情况,例如charAt:

public char charAt(int index) {
    if (isLatin1()) {
        return StringLatin1.charAt(value, index);
    } else {
        return StringUTF16.charAt(value, index);
    }
}

压缩字符串默认启用并且可以部分禁用 - “部分”因为它们仍然由 byte[] 支持并且返回 chars 的操作仍然必须将它们从两个单独的字节放在一起(由于内在函数,它是很难说这是否会对性能产生影响)。

更多

如果您对紧凑字符串的更多背景感兴趣,我建议您阅读上面链接的the interview 和/或观看this great talk by the same Aleksey Shipilëv(这也解释了新的字符串连接)。

【讨论】:

  • ...您会认为他们只需将内部编码切换为 UTF-8 就可以了...但是不,他们必须让它变得困难和混乱。
  • @jpmc26 您如何建议这样做,同时保留索引到字符串的性能和行为?
  • @jpmc26 你真的应该看the talk,因为它解决了那个明显但不好的主意。 UTF-8 是一种可变长度编码,因此如果不遍历内部数组,就无法确定charAt(i)。这正是 Eugene 和我一直在谈论的性能回归,必须不惜一切代价避免,否则整个功能都会死产。
  • @Nicolai,UTF-16 也是如此
  • @Basilevs 是的,但 Java char 始终是一个 UTF-16 代码点("?".charAt(1) 实际上会返回一些内容),因此 Java 不包含该属性。不过,为了使用 UTF-8 保护空间,它必须这样做。
【解决方案2】:

XX:+UseCompressedStringsCompact Strings 是不同的东西。

UseCompressedStrings 意味着只能将 ASCII 字符串转换为 byte[],但默认情况下这是关闭的。在 jdk-9 中,这种优化总是开启的,但不是通过标志本身,而是内置的。

直到 java-9 字符串在内部存储为 UTF-16 编码的char[]。从 java-9 起,它们将被存储为 byte[]。为什么?

因为在ISO_LATIN_1 中,每个字符都可以用单个字节(8 位)编码,而不是以前的编码(16 位,每个字符有 8 个从未使用过)。这适用于ISO_LATIN_1,但这是使用的大多数字符串。

这样做是为了空间使用。

这是一个小例子,应该让事情更清楚:

class StringCharVsByte {
    public static void main(String[] args) {
        String first = "first";
        String russianFirst = "первыи";

        char[] c1 = first.toCharArray();
        char[] c2 = russianFirst.toCharArray();

        for (char c : c1) {
            System.out.println(c >>> 8);
        }

        for (char c : c2) {
            System.out.println(c >>> 8);
        }
    }
}

在第一种情况下,我们将只得到零,这意味着最高有效的 8 位是零;在第二种情况下,将有一个非零值,这意味着至少存在最高有效位 8 中的一位。

这意味着如果我们在内部将字符串存储为一个字符数组,那么实际上会浪费每个字符的一半。事实证明,有多个应用程序实际上因此浪费了大量空间。

您有一个由 10 个 Latin1 字符组成的字符串?您刚刚丢失了 80 位或 10 个字节。为了减轻这种字符串压缩。现在,这些字符串不会有空间损失。

在内部,这也意味着一些非常好的事情。为了区分 LATIN1UTF-16 的字符串,有一个字段 coder

/**
 * The identifier of the encoding used to encode the bytes in
 * {@code value}. The supported values in this implementation are
 *
 * LATIN1
 * UTF16
 *
 * @implNote This field is trusted by the VM, and is a subject to
 * constant folding if String instance is constant. Overwriting this
 * field after construction will cause problems.
 */
private final byte coder;

现在基于此length 的计算方式不同:

public int length() {
    return value.length >> coder();
}

如果我们的字符串仅为 Latin1,则 coder 将为零,因此值的长度(字节数组)就是字符的大小。非拉丁语除以二。

【讨论】:

    【解决方案3】:

    Compact Strings 将两全其美。

    从 OpenJDK 文档中提供的定义可以看出:

    新的 String 类将根据字符串的内容存储编码为 ISO-8859-1/Latin-1(每个字符一个字节)或 UTF-16(每个字符两个字节)的字符。编码标志将指示使用哪种编码。

    正如@Eugene 所提到的,大多数字符串都以Latin-1 格式编码,每个字符需要一个字节,因此不需要当前String 类实现中提供的整个2 字节空间。

    新的 String 类实现将从 UTF-16 char array 转变为 a byte array 加上一个编码标志字段。附加的编码字段将显示字符是使用 UTF-16 还是 Latin-1 格式存储的。

    这也得出结论,如果需要,我们还能够以 UTF-16 格式存储字符串。而这也成为 Java 6 的压缩字符串Java 9 的压缩字符串 的主要区别点,如压缩字符串 only byte[] 数组 em> 用于存储,然后表示为 pure ASCII

    【讨论】:

      【解决方案4】:

      压缩字符串 (-XX:+UseCompressedStrings)

      这是 Java 6 Update 21 中引入的一项可选功能,通过在每个字符的一个字节上仅编码 US-ASCII 字符串来提高 SPECjbb 的性能。

      可以通过-XX 标志 (-XX:+UseCompressedStrings) 启用此功能。当它被启用时,String.value 被更改为一个对象引用,并且将指向一个 byte[],对于仅包含 7 位 US-ASCII 字符的字符串,或者一个 char[]

      后来由于高维护和难以测试,它在 Java 7 中被删除。

      压缩字符串

      这是 Java 9 中引入的一项新功能,用于构建内存高效的字符串。

      在 Java 9 之前,String 类将字符存储在 char 数组中,每个字符使用两个字节,但从 Java 9 开始,新的 String 类将字符存储在 byte[](每个字符一个字节)或 char[](两个每个字符的字节数),基于字符串的内容,加上一个编码标志字段。如果字符串字符的类型为Latin-1,则将使用byte[],否则如果字符的类型为UTF-16,则将使用char[]。编码标志将指示使用哪种编码。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-05-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-13
        • 1970-01-01
        • 2019-10-15
        相关资源
        最近更新 更多