此代码:
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
方法返回字节数。
答案 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
。
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。
答案 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-16LE。 string
存储为一系列字符。每个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 b
比string 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"
正如所料。
<强>买者强>
StringInfo
和TextElementEnumerator
类中的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."