libiconv:在 UTF-8/UTF-16/UTF-32 之间转换时目标字节长度的安全估计?

libiconv: Safe estimation of target bytes-length when converting between UTF-8/UTF-16/UTF-32?

有大量关于 Unicode 代码单元、代码点等的信息,但我对使用字节流(libiconv 要求)转换组合字符、字素等仍然有点模糊。

目前我只对使用 libconv 的 iconv() 在 UTF-8/UTF-16/UTF-32 之间进行转换感兴趣,它需要源缓冲区和目标缓冲区的字节长度作为参数。

问题: 是否有一种安全的方法来快速计算目标缓冲区的最大可能字节长度,基于已经源缓冲区的已知字节长度?

比方说,使用已知的 u16byteslenu16buf 转换为 u8buf(不包括 0x0000 终止符,如果有的话)。在最坏的情况下,UTF-16 源缓冲区中每个代码点将有 1 个两字节单元,对应于 UTF-8 目标缓冲区中每个代码点有 4 个单字节单元。这足以安全地假设 UTF-8 目标缓冲区永远不会超过 2 * u16lenbytes 吗?

我实际上已经对此进行了试验并且似乎有效,但我不确定我是否遗漏了涉及组合字符和字素簇的极端情况。我的怀疑来自于我对这些东西是如何在这 3 种不同的编码中转换的无知。我的意思是,一个字素是否可能需要说 3 个 UTF-16 代码点,但在转换时需要 10 个 UTF-8 代码点?

在那种情况下,u16lenbytes加倍是不够的,对吧?如果是这样,是否有任何其他直接的方法来预先计算目标缓冲区的最大长度?

Question: Is there a safe way to calculate fast the maximum possible bytes-length of the target buffer, based on the already known bytes-length of the source buffer?

是的。

to UTF-8 to UTF-16 to UTF-32
from UTF-8 ×2 ×4
from UTF-16 ×1 ½ ×1
from UTF-32 ×1 ×1

您可以通过按代码点范围对其进行分解来自行计算。选择一个源列和目标列,并找出最大的比率。

Code Point UTF-8 length UTF-16 length UTF-32 length
0000…007F 1 2 4
0080…07FF 2 2 4
0800…FFFF 3 2 4
10000…10FFFF 4 4 4

组合字符和字素簇不会产生任何影响。编码只是将一系列 Unicode 标量值 转换为 字节,,它们非常简单。

请注意,转换为 UTF-16 时需要额外添加两个字节,转换为 UTF-32 时需要额外添加四个字节,因为这些编码会将 BOM U+FEFF 添加到文本的开头。 (如果您不想那样,请使用一种无​​ BOM 编码,例如 UTF-16BEUTF-16LE。)

I mean, is it possible for a grapheme to need say 3 UTF-16 codepoints but like 10 UTF-8 codepoints when converted?

没有。这意味着某种其他类型的转换,如分解。 标量值输入的数量等于标量值输出的数量,加上可能 U+FEFF 字节序标记开头。 (我说“标量值”而不是“代码点”,因为“标量值”不包括代理项。如果您正在转码可能有错误或可能是垃圾数据的文本,它不会改变结果的大小。)

可编码的Unicode码点:

  • UTF-8:1、2、3 或 4 个字节
  • UTF-16:2 或 4 个字节
  • UTF-32: 4 字节
  • (已过时):UCS-2:2 个字节(但某些代码点需要两个代理项)。

所以,作为初步估计,如果你有 UTF-16 字节的长度,你可以使用这样的公式是安全的:

byte_len_utf8 = 4 * byte_len_utf16 / 2

但这不是一个好方法:我们更清楚:只有当 UTF-16 是 4 字节长度时,UTF-8 才是 4 字节长度。所以我们有两种情况:4 * len / 43 * len / 2.

因此,如果在第一个公式中我们分配了双倍字节(如您所想),那么在第二个公式中,最大值仅为字节数的 1.5 倍。对于 Chinese/Japanese/Korean,您位于代码点的此类区域。