Nic*_*lai 71

压缩字符串(Java 6)和紧凑字符串(Java 9)都具有相同的动机(字符串通常有效地为Latin-1,因此浪费了一半空间)和目标(使这些字符串变小)但实现方式差别很大.

压缩字符串

一次采访中, AlekseyShipilëv(负责实现Java 9功能)对压缩字符串有这样的说法:

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

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

紧凑的弦乐

另一方面,在Java 9中,紧凑字符串完全集成到JDK源中.String始终后盾byte[],在字符使用一个字节,如果他们是拉丁语-1否则两项.大多数操作都会检查以查看是哪种情况,例如charAt:

public char charAt(int index) {
    if (isLatin1()) {
        return StringLatin1.charAt(value, index);
    } else {
        return StringUTF16.charAt(value, index);
    }
}
Run Code Online (Sandbox Code Playgroud)

紧凑字符串默认启用,可以部分禁用 - "部分",因为它们仍然由a支持byte[]和操作返回chars必须仍然将它们从两个单独的字节放在一起(由于内在函数,很难说这是否会影响性能).

更多

如果你对更多关于紧凑字符串的背景感兴趣,我建议你阅读我上面链接的访谈和/或观看同一个AlekseyShipilëv的精彩演讲(这也解释了新的字符串连接).

  • @ jpmc26你如何建议这样做,同时保留索引到字符串中的性能和行为? (6认同)
  • ...你认为他们只会将内部编码转换为UTF-8并完成......但不,他们不得不让它变得困难和混乱. (4认同)
  • @Basilevs True但是Java`char`总是一个UTF-16代码点(`"".charAt(1)`实际上返回了一些东西),因此Java不接受该属性.但是,为了安全地使用UTF-8,它必须这样做. (4认同)
  • @ jpmc26你应该真的看[谈话](https://www.youtube.com/watch?v=wIyeOaitmWM),因为它解决了这个明显但不好的想法.UTF-8是一种可变长度编码,因此无法在不迭代内部数组的情况下确定`charAt(i)`.这正是Eugene和我一直在谈论的性能回归,必须不惜一切代价避免,或者整个功能都是死产. (3认同)
  • @Nicolai,UTF-16也是如此 (3认同)
  • IIRC,在那一节他谈到了渐近性能,即没有使它从常数到线性时间.不断增加将满足该标准,并且至少非内在代码显示该特性,因为它必须移位和"或"某些字节. (2认同)

Eug*_*ene 24

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

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

直到java-9字符串在内部存储为char[]UTF-16编码.从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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在第一种情况下,我们将仅获得零,这意味着最重要的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;
Run Code Online (Sandbox Code Playgroud)

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

public int length() {
    return value.length >> coder();
}
Run Code Online (Sandbox Code Playgroud)

如果我们的String只是Latin1,则编码器将为零,因此值的长度(字节数组)是字符的大小.对于非拉丁语1除以2.


Dha*_*ria 7

Compact Strings将拥有两全其美.

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

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

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

新的String类的实现将转移UTF-16 char arraya byte array 加编码标记字段.附加编码字段将显示是使用UTF-16还是Latin-1格式存储字符.

这也得出结论,如果需要,我们还能够以UTF-16格式存储字符串.而这也成为之间差异的主要点的Java 6的压缩字符串爪哇9的紧凑字符串作为压缩字符串仅byte []数组被用于存储,然后将其作为representated 纯ASCII.