7

引用 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 字节,因此这将给出不正确的结果。
4

1 回答 1

12

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

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

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

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

于 2015-06-03T12:16:34.627 回答