【问题标题】:Length() vs Sizeof() on Unicode stringsUnicode 字符串上的 Length() 与 Sizeof()
【发布时间】:2015-08-17 15:30:03
【问题描述】:

引用 Delphi XE8 帮助:

对于单字节和多字节字符串,Length 返回字符串使用的字节数。 UTF-8 示例:

   Writeln(Length(Utf8String('1¢'))); // displays 3

对于 Unicode (WideString) 字符串,Length 返回字节数除以 2。

这就引出了重要的问题:

  1. 为什么在处理上会存在差异?
  2. 在某些情况下,为什么 Length() 没有按预期执行,只返回参数的长度(如元素的计数)而不是以字节为单位给出大小?
  3. 为什么它声明它将结果除以 2 用于 Unicode (UTF-16) 字符串? AFAIK UTF-16 最多为 4 字节,因此这会给出不正确的结果。

【问题讨论】:

  • 尝试 LenInBytes := Length(UTF8Encode('строка')) var u8: UTF8String; u8 := 'строка'; I := Length(u8) - 不进行类型转换
  • 我写这个是因为它以编码的名字写着,编码一个字符需要多少时间。我只是将它与另一种编码混淆了。
  • 我已恢复您的所有编辑。主要是因为我不特别想不断更新我的答案以匹配! ;-) 无论如何,我认为很明显你现在已经掌握了这一点。这个问题很好。我们不能保持原样吗?

标签: delphi delphi-xe8


【解决方案1】:

Length 在将字符串视为数组时返回元素的数量。

  • 对于具有 8 位元素类型(ANSI、UTF-8)的字符串,Length 会为您提供字节数,因为字节数与元素数相同。
  • 对于具有 16 位元素 (UTF-16) 的字符串,Length 是字节数的一半,因为每个元素都是 2 字节宽。

您的字符串“1¢”有两个代码点,但第二个代码点需要两个字节才能以 UTF-8 对其进行编码。因此,Length(Utf8String('1¢')) 的计算结果为 3。

您在问题标题中提到了SizeOf。将字符串变量传递给SizeOf 将始终返回指针的大小,因为字符串变量实际上只是一个指针。

针对您的具体问题:

为什么会有处理上的差异?

如果您认为Length 与字节相关,则只有区别。但这是错误的思考方式Length 总是返回一个元素计数,当这样看时,所有字符串类型的行为都是一致的,实际上所有数组类型的行为都是一致的。

为什么 Length() 没有按照预期做,只返回参数的长度(如元素的数量),而不是在某些情况下以字节为单位给出大小?

它总是返回元素计数。碰巧当元素大小为单个字节时,元素计数和字节计数恰好相同。事实上,您引用的文档还包含您提供的摘录上方的以下内容:返回字符串中的字符数或数组中的元素数。这是关键文本。您包含的摘录旨在说明此斜体文本的含义。

为什么它声明它将结果除以 2 用于 Unicode (UTF-16) 字符串? AFAIK UTF-16 最多为 4 字节,因此这将给出不正确的结果。

UTF-16 字符元素总是 16 位宽。但是,某些 Unicode 代码点需要两个字符元素进行编码。这些字符元素对称为代理对。


我认为,您希望Length 将返回字符串中的代码点数。但事实并非如此。它返回字符元素的数量。并且对于可变长度编码,码点的数量不一定与字符元素的数量相同。如果您的字符串被编码为 UTF-32,那么代码点的数量将与字符元素的数量相同,因为 UTF-32 是一个恒定大小的编码。

计算代码点的一种快速方法是扫描字符串,检查代理对。当您遇到代理对时,请计算一个代码点。否则,当您遇到不属于代理对的字符元素时,请计算一个代码点。在伪代码中:

N := 0;
for C in S do
  if C.IsSurrogate then
    inc(N)
  else
    inc(N, 2);
CodePointCount := N div 2;

另外一点是代码点计数与可见字符计数不同。一些代码点正在组合字符,并与它们相邻的代码点组合以形成单个可见字符或字形。

最后,如果您只想找到字符串有效负载的字节大小,请使用以下表达式:

Length(S) * SizeOf(S[1])

此表达式适用于所有类型的字符串。

要非常小心函数System.SysUtils.ByteLength。从表面上看,这似乎正是你想要的。但是,该函数返回 UTF-16 编码字符串的字节长度。因此,如果您将AnsiString 传递给它,那么ByteLength 返回的值是AnsiString 字节数的两倍。

【讨论】:

  • 看我问题中的代码。 “1¢”只有两个字符长,但输出仍然是 3。
  • @ZzZombo 没错。您有两个代码点,但 UTF-8 编码的字节数组的长度为 3。
  • Length(s)*SizeOf(s[1]) 为您提供字符串占用的字节数。
  • @ZzZombo 不要那样做!这将涉及毫无意义的堆分配。使用我之前评论中的简单表达式。
  • 没有。对于空字符串也可以。然后它返回0SizeOf() 在编译时评估。
最近更新 更多