这段代码:
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
方法返回的是字节数。
这段代码:
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
方法返回的是字节数。
其他人都只回答了表面问题,但实际上还有更深层次的原因:计算“字符”数量是一个难以定义的问题,并且可能会非常耗费计算资源,而长度属性应该更快速。
为什么这个问题难以定义?嗯,有几个选择,且都没有比另一个更合适:
代码单元的数量(字节数或其他固定大小的数据块;C# 和 Windows 通常使用 UTF-16,因此返回两个字节的数量)肯定是相关的,因为计算机在许多目的中仍需要以该形式处理数据(比如写入文件就关心字节而不是字符)
Unicode 代码点的数量相对容易计算(虽然是 O(n) 因为你必须扫描字符串寻找代理对),并且可能对于文本编辑器来说很重要...但实际上并不等同于打印在屏幕上的字符数(称为 grapheme)。例如,一些带重音符号的字母可以用两种形式表示:单个代码点或成对出现的两个点,一个代表字母,另一个表示“给我的伙伴字母加一个重音符号”。那么这一对算两个字符还是一个字符呢?您可以通过规范化字符串来帮助解决这个问题,但并不是所有有效字母都有单个代码点表示。
甚至 grapheme 的数量也不等于打印字符串的长度,这取决于字体等因素,而且由于某些字符在许多字体上具有一定重叠(字距),因此屏幕上的字符串长度不一定等于 grapheme 长度之和!
有些 Unicode 点甚至不是传统意义上的字符,而是某种控制标记。比如字节顺序标记或从右到左的指示器。这些算吗?
总之,字符串长度实际上是一个极其复杂的问题,并计算它可能需要大量的 CPU 时间以及数据表。
此外,这些指标有什么意义?为什么这些指标很重要?好吧,只有您能够回答您的情况,但就我个人而言,它们通常是不相关的。我发现通过字节限制来限制数据输入更有逻辑性,因为这正是需要传输或存储的内容。限制显示大小最好由显示端软件完成 - 如果您有100个像素用于消息,则可以容纳多少字符取决于字体等因素,这些因素在数据层软件中是未知的。最后,鉴于Unicode标准的复杂性,如果尝试其他任何方法,您可能会在边缘情况下遇到错误。因此,这是一个难以回答且没有很多通用用途的问题。代码单元数量很容易计算 - 它只是底层数据数组的长度 - 并且作为一般规则具有最有意义/有用的简单定义。
这就是为什么“b”超出“文档如此说”的表面解释而具有长度4的原因。
根据 String.Length 属性的 documentation
文档:
Length属性返回此实例中Char对象的数量,而不是Unicode字符的数量。这是因为一个Unicode字符可能由多个Char表示。使用System.Globalization.StringInfo类来处理每个Unicode字符,而不是每个Char。
String b
也打印出4),因为它在字符数组中使用UTF-16表示法。在UTF-8中,它是一个4字节的字符。 - Michael在"AC"
中索引为1的字符是一个SurrogatePair
需要记住的关键点是代理对表示32位单个字符。
您可以尝试此代码,它将返回True
Console.WriteLine(char.IsSurrogatePair("AC", 1));
Char.IsSurrogatePair方法(String,Int32)
如果s参数包括索引位置和索引+1处的相邻字符,并且位置index上的字符的数值范围从U+D800到U+DBFF,并且位置index+1处的字符的数值范围从U+DC00到U+DFFF,则返回
true
;否则返回false
。
这在String.Length属性中进一步解释:
Length属性返回此实例中Char对象的数量,而不是Unicode字符的数量。原因是一个Unicode字符可能由多个Char表示。使用System.Globalization.StringInfo类来处理每个Unicode字符,而不是每个Char。
正如其他答案所指出的那样,即使有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
对象,并且打印出的结果与原字符串不一致。
Length
属性返回的是字符对象而不是Unicode字符数,因此出现了这种情况。在您的情况下,一个Unicode字符由多个char对象(SurrogatePair)表示。
Length属性返回此实例中Char对象的数量,而不是Unicode字符的数量。原因是Unicode字符可能由多个Char表示。使用System.Globalization.StringInfo类来处理每个Unicode字符,而不是每个Char。
正如其他人所说,它不是字符串中字符的数量,而是Char对象的数量。字符 ” 是码点U+20213。由于该值在16位char类型的范围之外,因此编码为UTF-16作为代理对D840 DE13
。
获取字符长度的方法在其他答案中已经提到。但是应谨慎使用,因为Unicode中有许多表示字符的方式。例如,“à”可能是1个组合字符或2个字符(a + 音标)。可能需要进行规范化,就像Twitter的情况一样。
您应该阅读这篇文章:
关于Unicode和字符集,每个软件开发人员绝对必须知道的最基本的知识(没有借口!)
length()
仅适用于Unicode码点,其大小不超过U+FFFF
。 这组码点称为基本多文种平面(BMP),仅使用2个字节。BMP
之外的Unicode码点使用4个字节的代理对在UTF-16中表示。StringInfo
。StringInfo b = new StringInfo("AC");
Console.WriteLine(string.Format("Length 2 = {0}", b.LengthInTextElements));
好的,在.Net和C#中所有字符串都被编码为UTF-16LE。一个string
被存储为一系列字符。每个char
封装了2个字节或16位的存储。
我们在纸上或屏幕上看到的作为单个字母、字符、字形、符号或标点符号的东西可以被视为单个文本元素。如Unicode标准附录#29 UNICODE TEXT SEGMENTATION所述,每个文本元素由一个或多个代码点表示。可以在此处找到完整的代码列表。
每个码点都需要编码为二进制以便计算机进行内部表示。如前所述,每个char
存储2个字节。在或等于U+FFFF
的代码点可以存储在单个char
中。在U+FFFF
以上的代码点将作为代理对存储,使用两个字符表示一个单独的代码点。char
,作为两个字符的代理对,或者,如果文本元素由多个代码点表示,则是单个字符和代理对的某种组合。如果还不够复杂,一些文本元素可以用不同的代码点组合来表示,如Unicode标准附录#15,Unicode规范化形式所述。
插曲
因此,看起来相同的字符串实际上可以由不同的字符组合而成。对这样两个字符串进行顺序(逐字节)比较会检测到差异,这可能是意外或不希望的。
您可以重新编码 .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"
注意事项
.Net实现的Unicode文本分割在StringInfo
和TextElementEnumerator
类中应该是普遍有用的,在大多数情况下,会产生调用者期望的响应。然而,正如Unicode标准附录#29所述,“由于仅根据文本无法始终准确地匹配用户感知,因此目标不能总是完全达成。”