【问题标题】:Why is the length of this string longer than the number of characters in it?为什么这个字符串的长度比它的字符数长?
【发布时间】:2015-01-14 13:04:21
【问题描述】:

这段代码:

string a = "abc";
string b = "A????C";
Console.WriteLine("Length a = {0}", a.Length);
Console.WriteLine("Length b = {0}", b.Length);

输出:

Length a = 3
Length b = 4

为什么?我唯一能想到的就是汉字有2个字节长,.Length方法返回字节数。

【问题讨论】:

  • 只看标题我怎么知道这是代理对问题。啊,好'ol System.Globalization 是你的盟友!
  • 在 UTF-16 中是 4 个字节长,而不是 2 个
  • char ???? 的十进制值为 131603,因为 chars 是无符号字节,这意味着您可以用 2 个字符而不是 4 个字符来实现该值(无符号 16 位值最大值为 65535(或65536 个变体)并使用 2 个字符来表示它允许的最大变体数量不是 65536*2(131072),而是 65536*65536 个变体(4,294,967,296,实际上是 32 位值)
  • @GMAsucci: UTF-16 是 2 个字符,但是 4 个字节,因为一个 UTF16 字符是 2 个字节,否则它无法存储 65536 个变体,而只能存储 256 个。
  • 我推荐阅读伟大的文章“每个软件开发人员绝对、肯定必须了解 Unicode 和字符集的绝对最低要求(没有任何借口!)”joelonsoftware.com/articles/Unicode.html

标签: c# .net string unicode unicode-string


【解决方案1】:

来自String.Length 属性的documentation

Length 属性返回此实例中 Char 对象的数量,而不是 Unicode 字符的数量。原因是一个 Unicode 字符可能由多个 Char 表示。使用 System.Globalization.StringInfo 类来处理每个 Unicode 字符,而不是每个 Char

【讨论】:

  • Java 的行为方式相同(也为 String b 打印 4),因为它在 char 数组中使用 UTF-16 表示。它是 UTF-8 中的 4 字节字符。
【解决方案2】:

这是因为Length 属性返回char 对象 的数量,而不是Unicode 字符的数量。在您的情况下,Unicode 字符之一由多个 char 对象(SurrogatePair)表示。

Length 属性返回此中 Char 对象的数量 例如,不是 Unicode 字符的数量。原因是一个 Unicode 字符可能由多个 Char 表示。使用 System.Globalization.StringInfo 类与每个 Unicode 一起工作 字符而不是每个字符。

【讨论】:

  • 您在此答案中对“字符”的使用模棱两可。我建议至少用精确的术语替换第一个。
  • 谢谢。修正了歧义。
【解决方案3】:

"A?C" 中索引 1 处的角色是 SurrogatePair

要记住的关键点是代理对代表 32 位 单个字符。

你可以试试这个代码,它会返回True

Console.WriteLine(char.IsSurrogatePair("A?C", 1));

Char.IsSurrogatePair Method (String, Int32)

true 如果 s 参数在位置包含 相邻字符 index 和 index + 1,以及字符的数值 位置索引范围从 U+D800 到 U+DBFF,数字 位置 index+1 处字符的值范围从 U+DC00 到 U+DFFF;否则,false

这在String.Length 属性中有进一步解释:

Length 属性返回 此中 Char 对象的数量 例如,不是 Unicode 字符的数量。 原因是 Unicode 字符可能由多个 Char 表示。使用 System.Globalization.StringInfo 类与每个 Unicode 一起工作 字符而不是每个字符。

【讨论】:

    【解决方案4】:

    正如其他答案所指出的,即使有 3 个可见字符,它们也用 4 个 char 对象表示。这就是为什么Length 是 4 而不是 3。

    MSDN 声明

    Length 属性返回此中 Char 对象的数量 例如,不是 Unicode 字符数。

    但是,如果您真正想知道“文本元素”的数量而不是 Char 对象的数量,您可以使用 StringInfo 类。

    var si = new StringInfo("A?C");
    Console.WriteLine(si.LengthInTextElements); // 3
    

    你也可以像这样枚举每个文本元素

    var enumerator = StringInfo.GetTextElementEnumerator("A?C");
    while(enumerator.MoveNext()){
        Console.WriteLine(enumerator.Current);
    }
    

    在字符串上使用foreach 会将中间的“字母”拆分为两个char 对象,并且打印的结果将与字符串不对应。

    【讨论】:

      【解决方案5】:

      其他人都给出了表面上的答案,但也有更深层次的理由:“字符”的数量是一个难以定义的问题,计算起来可能非常昂贵,而长度属性应该很快。

      为什么很难定义?好吧,有几个选项,没有一个比另一个更有效:

      • 代码单元的数量(字节或其他固定大小的数据块;C# 和 Windows 通常使用 UTF-16,因此它返回两字节块的数量)当然是相关的,因为计算机仍然需要处理出于多种目的使用该形式的数据(例如,写入文件,关心字节而不是字符)

      • Unicode 代码点的数量相当容易计算(尽管 O(n),因为您必须扫描字符串以查找代理对)并且可能对文本编辑器很重要....但实际上并不相同事物作为打印在屏幕上的字符数(称为字素)。例如,一些重音字母可以用两种形式表示:一个代码点,或者两个点配对在一起,一个代表字母,一个表示“给我的伙伴字母添加重音”。这对是两个字符还是一个?您可以规范化字符串以帮助解决此问题,但并非所有有效字母都具有单个代码点表示。

      • 即使字素的数量与打印字符串的长度也不相同,这取决于字体以及其他因素,并且由于在打印某些字符时在许多字体中存在一些重叠(字距调整),因此无论如何,屏幕上字符串的长度不一定等于字素长度的总和!

      • 一些 Unicode 点甚至不是传统意义上的字符,而是某种控制标记。像字节顺序标记或从右到左的指示符。这些算不算?

      简而言之,字符串的长度实际上是一个非常复杂的问题,计算它可能需要大量的 CPU 时间以及数据表。

      此外,还有什么意义?为什么这些指标很重要?好吧,只有你可以为你的情况回答这个问题,但就我个人而言,我发现它们通常是无关紧要的。我发现限制数据输入在逻辑上更符合字节限制,因为无论如何这都是需要传输或存储的。限制显示大小最好由显示端软件完成 - 如果您有 100 个像素的消息,您适合多少个字符取决于字体等,无论如何数据层软件都不知道。最后,鉴于 unicode 标准的复杂性,如果您尝试其他任何方法,您可能会在边缘情况下遇到错误。

      所以这是一个难题,没有很多通用用途。代码单元的数量很容易计算 - 它只是底层数据数组的长度 - 作为一般规则,最有意义/最有用的,具有简单的定义。

      这就是为什么b 的长度4 超出了“因为文档这么说”的表面解释。

      【讨论】:

      • 本质上,'.Length' 并不是大多数程序员认为的那样。也许应该有一组更具体的属性(例如 GlyphCount)和标记为过时的长度!
      • @locster 我同意,但不要认为 Length 应该过时,以保持与数组的类比。
      • @locster 它不应该过时。 python 很有意义,没有人质疑它。
      • 我认为 .Length 很有意义,并且是一种自然属性,只要您了解它是什么以及为什么会这样。然后它就像任何其他数组一样工作(在像 D 这样的某些语言中,就语言而言,字符串字面意思是一个数组,它工作得非常好)
      • 这不是真的(一个常见的误解) - 使用 UTF-32 时,lengthInBytes / 4 会给出 代码点 的数量,但那不是 与“字符”或字素的数量相同。考虑拉丁小写字母 E 后跟一个组合分音符号...打印为单个字符,它甚至可以标准化为单个代码点,但它仍然是两个单位长,即使在 UTF-32 中也是如此。
      【解决方案6】:

      正如其他人所说,这不是字符串中的字符数,而是 Char 对象的数量。字符 ? 是代码点 U+20213。由于该值超出了 16 位 char 类型的范围,因此它以 UTF-16 编码为代理对 D840 DE13

      其他答案中提到了获取字符长度的方法。但是应该小心使用它,因为可以有多种方式来表示 Unicode 中的字符。 “à”可以是 1 个组合字符或 2 个字符(a + 变音符号)。可能需要规范化,例如 twitter

      您应该阅读此内容
      The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

      【讨论】:

        【解决方案7】:

        这是因为length() 仅适用于不大于U+FFFF 的Unicode 代码点。这组代码点称为Basic Multilingual Plane (BMP),仅使用 2 个字节。

        BMP 之外的 Unicode 代码点在 UTF-16 中使用 4 字节代理对表示。

        要正确计算字符数 (3),请使用 StringInfo

        StringInfo b = new StringInfo("A?C");
        Console.WriteLine(string.Format("Length 2 = {0}", b.LengthInTextElements));
        

        【讨论】:

          【解决方案8】:

          好的,在 .Net 和 C# 中,所有字符串都编码为 UTF-16LEstring 存储为字符序列。每个char封装了2字节或16位的存储。

          我们在“纸上或屏幕上”看到的单个字母、字符、字形、符号或标点符号可以被视为单个文本元素。如Unicode Standard Annex #29 UNICODE TEXT SEGMENTATION 中所述,每个文本元素由一个或多个代码点表示。代码的详尽列表可以是found here

          每个代码点都需要编码成二进制,以便计算机内部表示。如前所述,每个 char 存储 2 个字节。 U+FFFF 或以下的代码点可以存储在单个char 中。 U+FFFF 以上的代码点存储为代理对,使用两个字符表示单个代码点。

          鉴于我们现在知道的可以推断,文本元素可以存储为一个 char,作为两个字符的代理对,或者,如果文本元素由多个代码点表示,则单个字符和代理的某种组合对。好像这还不够复杂,一些文本元素可以由代码点的不同组合表示,如in, Unicode Standard Annex #15, UNICODE NORMALIZATION FORMS 所述。


          插曲

          因此,在渲染时看起来相同的字符串实际上可以由不同的字符组合组成。两个这样的字符串的序数(逐字节)比较会检测到差异,这可能是意外或不受欢迎的。

          您可以重新编码 .Net 字符串。以便他们使用相同的规范化表格。标准化后,具有相同文本元素的两个字符串将以相同的方式编码。为此,请使用string.Normalize 函数。但是,请记住,一些不同的文本元素看起来彼此相似。 :-s


          那么,对于这个问题,这一切意味着什么?文本元素'?' 由单个代码点 U+20213 cjk 统一表意文字扩展 b 表示。这意味着它不能编码为单个char,必须使用两个字符编码为代理对。这就是为什么string bchar 长一个string a

          如果您需要可靠地(请参阅警告)计算 string 中的文本元素的数量,您应该使用 System.Globalization.StringInfo 类这样的。

          using System.Globalization;
          
          string a = "abc";
          string b = "A?C";
          
          Console.WriteLine("Length a = {0}", new StringInfo(a).LengthInTextElements);
          Console.WriteLine("Length b = {0}", new StringInfo(b).LengthInTextElements);
          

          给出输出,

          "Length a = 3"
          "Length b = 3"
          

          正如预期的那样。


          警告

          StringInfoTextElementEnumerator 类中Unicode 文本分割的.Net 实现通常很有用,并且在大多数情况下,会产生调用者期望的响应。但是,如Unicode Standard Annex #29, "The goal of matching user perceptions cannot always be met exactly because the text alone does not always contain enough information to unambiguously decide boundaries."中所述

          【讨论】:

          • 我认为您的回答可能令人困惑。在这种情况下,?只是单个码点,但由于其码点超过 0xFFFF,因此必须使用代理对表示为 2 个码元。字形是建立在代码点之上的另一个概念,其中一个字形可以由单个代码点或多个代码点表示,如韩语的韩文或许多基于拉丁语的语言中所见。
          • @nhahtdh,我同意,我的回答是错误的。我已经重写了它,希望它现在可以更加清晰。
          猜你喜欢
          • 1970-01-01
          • 2021-07-31
          • 1970-01-01
          • 1970-01-01
          • 2021-05-26
          • 2014-03-06
          • 2016-05-12
          • 1970-01-01
          • 2013-02-12
          相关资源
          最近更新 更多