初始化 StringBuilder 以使用 UTF-16 编码器

Initializing StringBuilder to use UTF-16 coder

考虑 Java11 中的以下代码:

StringBuilder sb = new StringBuilder("one");
sb.append("δύο");  // "two"

第一行创建一个使用 Latin1 编码器的 StringBuilder(每个字符一个字节)。然后第二行使 StringBuilder 意识到它需要改用 UTF16 编码器,因此它在附加新的 UTF-16 字符之前将其当前内容复制到一个新数组中。

StringBuilder class 有一个构造函数重载,它接受一个初始容量参数,如果您已经知道要构建的字符串的所需大小,它旨在避免重新分配。但是如果你从一个英文字符串开始,然后附加一个外国字符串,这个特殊的构造函数重载就没有用了,因为它仍然重新分配字节数组。

有没有办法创建一个从一开始就使用 UTF16 的 StringBuilder 实例?

好像没有明显的。如果您想影响 StringBuffer 的初始化方式,我的建议是创建一个实现 CharSequence 的实用程序 'initializer' 并使用相应的 StringBuilder 构造函数。您可以使用它来传达任何长度和字符内容,并且 StringBuilder 内部应该足够聪明以接受它。

不过看看 OpenJDK 11 的实现,似乎 hell-bent 无论如何都从 Latin1 开始。某种形式的重新分配似乎总是会发生。

Java 11 或 Java 12 版本的 StringBuilder 中没有任何内容可以执行此操作。

真正的问题是您可能从中获得的性能提升对您有多重要。 分析您的应用程序 以查明这种不需要的重新分配是否对您应用程序的整体 性能有显着影响。

如果要产生显着差异,您可以实现自己的版本 StringBuilder(扩展相同的接口以实现兼容性)。

或者,如果您准备等待,您可以下载 OpenJDK 源代码并开发/构建/测试 StringBuilder 的扩展...并将其作为补丁提交以供考虑。 (如果您包含了显示出明显性能优势的基准,那将有助于增加包含的机会。)

对此做了更多的研究,我为我自己的问题提供了另一个答案(Stack Overflow 说回答你自己的问题是完全可以接受的。)

一样,StringBuilder 无论如何都会使用 Latin1 进行初始化。因此,假设您主要使用俄语、中文、印地语或希腊语等语言写作。你想构建一个你已经知道最大大小的字符串,所以你使用初始容量参数:

StringBuilder sb = new StringBuilder(4096);
sb.append("Здравствуйте!");  // Should easily fit in 4 kilobytes, right?

然而,上面对 append 的调用丢弃了您之前初始化的 4KB 缓冲区并分配了一个新缓冲区。您构造了具有初始容量的 StringBuilder 以避免重新分配缓冲区,但 StringBuilder 无论如何都重新分配了它。它重新分配了它,即使它已经足够大了!

解决方法是 运行 java 使用 JVM 选项 -XX:-CompactStrings.

如果您一直使用其中一种语言,那么您的字符串无论如何都会使用 UTF-16,因此在启动时关闭字符串压缩将减少检查您提供的每个字符串以查看它是否可以使用 Latin1 存储的开销编码。

另请参阅 Heinz Kabutz's talk at jPrime Bulgaria, 29 May 2019,他导致 StringBuilder 运行 由于此 "feature" 内存不足。