为什么这个字符串的长度比它中的字符数长?

时间:2014-11-17 15:13:54

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

此代码:

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

输出:

Length a = 3
Length b = 4

为什么呢?我唯一能想到的是中文字符长2个字节,.Length方法返回字节数。

8 个答案:

答案 0 :(得分:231)

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

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

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

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

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

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

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

而且,重点是什么?为什么这些指标很重要?好吧,只有你可以回答你的情况,但就个人而言,我发现它们通常是无关紧要的。我发现限制数据输入更符合逻辑上的字节限制,因为无论如何都需要传输或存储。显示器侧软件可以更好地限制显示器尺寸 - 如果您有100个像素的消息,您所适合的字符数取决于字体等,无论如何数据层软件都不知道。最后,考虑到unicode标准的复杂性,如果你尝试其他任何东西,你可能会在边缘情况下遇到错误。

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

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

答案 1 :(得分:61)

来自String.Length媒体资源的documentation

  

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

答案 2 :(得分:32)

"AC"中索引1处的角色是SurrogatePair

  

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

您可以尝试使用此代码,它将返回True

Console.WriteLine(char.IsSurrogatePair("AC", 1));

Char.IsSurrogatePair Method (String, Int32)

  

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

String.Length属性:

进一步说明了这一点
  

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

答案 3 :(得分:23)

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

MSDN声明

  

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

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

var si = new StringInfo("AC");
Console.WriteLine(si.LengthInTextElements); // 3

您还可以枚举像这样的每个文本元素

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

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

答案 4 :(得分:20)

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

  

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

答案 5 :(得分:10)

正如其他人所说,它不是字符串中的字符数,而是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!)

答案 6 :(得分:6)

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

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

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

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

答案 7 :(得分:6)

好的,在.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 bstring a长一char的原因。

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

using System.Globalization;

string a = "abc";
string b = "AC";

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."

中所述