确定从UTF-8字符串转换为UTF-16字符串所需的字节数的最有效方法是什么?

6
我看到有一些非常聪明的代码可以在Unicode码点和UTF-8之间进行转换,所以我想知道是否有人已经(或者乐于)开发了这个功能。
  • 给定一个UTF-8字符串,需要多少字节才能将其编码为相同字符串的UTF-16。
  • 假设UTF-8字符串已经过验证。它没有BOM,没有过长序列,没有无效序列,并且以空字符结尾。它不是CESU-8
  • 必须支持完整的带代理项的UTF-16。

具体来说,我想知道是否有捷径可以知道何时需要使用代理项对而无需完全将UTF-8序列转换为码点。

我见过的最好的UTF-8到码点的代码使用了向量化技术,所以我想知道这里是否也可能。


“无效序列”是否包括“UTF-8编码中没有代理值”? - Joachim Sauer
@Joachim:是的。“它不是CESU-8”。 - hippietrail
1
啊,我明白了,我之前不知道那个术语。 - Joachim Sauer
@Karl:因为我理想中想要一个低级别的C语言解决方案。我希望鼓励使用指针而不是调用系统函数。特别是我想要类似于这个的东西:http://www.daemonology.net/blog/2008-06-05-faster-utf8-strlen.html(但其他语言或与语言无关的想法的讨论也是受欢迎的)。 - hippietrail
3个回答

5
效率总是速度与大小的权衡。如果优先考虑速度而不是大小,那么最有效的方法就是根据源字符串的长度进行猜测。
需要考虑4种情况,只需将最坏的情况作为最终缓冲区大小:
- U+0000-U+007F - 在utf8中编码为1个字节,在utf16中每个字符编码为2个字节。(1:2 = x2) - U+0080-U+07FF - 编码为2个字节的utf8序列,或者在utf16字符中每个字符编码为2个字节。(2:2 = x1) - U+0800-U+FFFF - 在utf8中存储为3个字节的序列,但仍适合单个utf16字符。(3:2 = x.67) - U+10000-U+10FFFF - 在utf8中存储为4个字节的序列,或在utf16中存储为代理对。(4:4 = x1)
最坏的扩展因子是将U+0000-U+007f从utf8转换为utf16:按字节算,缓冲区只需比源字符串大两倍。当将任何其他unicode代码点编码为utf16时,结果都是相等大小或更小的字节分配。

“最有效”的定义很大程度上取决于计算成本与内存成本的优先级设置,但我同意:一般来说,这很可能是最佳方法。 - Joachim Sauer
我曾经想到过这个,但没有全面分析它。现在你说出来,确实看起来非常简单。谢谢! - hippietrail
我推迟接受你的答案,因为我希望有人能想出一个聪明的向量化算法。显然,你的方法在时间上是无与伦比的,但对于那些不使用ASCII字符之外的许多字符的语言来说,最坏情况下会使用接近于双倍的内存。 - hippietrail

3
非常简单:计算头字节的数量,双重计数字节 F0 及以上。
代码如下:
size_t count(unsigned char *s)
{
    size_t l;
    for (l=0; *s; s++) l+=(*s-0x80U>=0x40)+(*s>=0xf0);
    return l;
}

注意:此函数返回UTF-16代码单元的长度。如果您想要所需字节数,请乘以2。如果您将要存储空终止符,您还需要考虑其所占用的空间(一个额外的代码单元/两个额外的字节)。

+1:我认为你的代码并不简单,但它非常简洁。当我使用纸张检查你的位操作时,所有的操作都似乎是正确的。只有一个吹毛求疵的错误。你的函数返回的是16位代码单元的数量,而不是字节数(这才是问题所要求的)。返回 l * 2 就可以解决这个问题。 - Ciaran McHale
事实上,我没有注意到 OP 要求字节。 - R.. GitHub STOP HELPING ICE

2
这不是一个算法,但如果我理解正确,规则如下:
每个以 MSB 为0的字节会增加2个字节(1个UTF-16代码单元)
该字节表示U+0000 - U+007F范围内的单个Unicode代码点
每个以MSBs 110或1110开头的字节都会增加2个字节(1个UTF-16代码单元)
这些字节分别开始2字节和3字节序列,代表U+0080 - U+FFFF范围内的Unicode代码点
每个4个MSB设置的字节(即以1111开头的字节)会增加4个字节(2个UTF-16代码单元)
这些字节开始4字节序列,覆盖了“其余”的Unicode范围,可以用UTF-16中的低位替代符和高位替代符表示
其他每个字节(即以10开头的字节)都可以跳过
这些字节已经与其他字节一起计算。
我不是C语言专家,但这看起来很容易向量化。

1
看起来不错,但你提到的“5或6字节序列”已经在几年前从合法的UTF-8中删除了,而你没有提到4字节序列。 - hippietrail
1
缺失的数字4是一个打字错误(它应该与数字5和6分组),而且不再允许使用5/6字节序列并不真正改变算法:它们只是与4字节序列分组,并且无论如何都会产生相同的结果。(即我用“4字节”替换“5或6字节”;-)) - Joachim Sauer
看起来Unicode范围> = U + 10000确实是UTF-8 4字节范围和“补充平面”的开头,这就是代理所覆盖的内容。一个完美的快捷方式(-: - hippietrail

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接