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

这个代码:

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方法返回字节数。

26337 次浏览

String.Length属性的 文件:

Llength 属性返回此实例中 夏尔对象的数目,而不是 Unicode 字符的数目。原因在于,一个 Unicode字符可能由不止一个 ABc0来代表。使用 System。全球化。 StringInfo类来处理每个 Unicode字符,而不是每个 夏尔

这是因为 Length属性返回的是 Char 对象的编号,而不是 unicode 字符的编号。在您的示例中,一个 Unicode 字符由多个字符对象(SurrogatePair)表示。

属性返回此 实例,而不是 Unicode 字符的数量。原因是 Unicode字符可以由多个字符表示 System.Globaly.StringInfo 类使用每个 Unicode 字符而不是每个字符。

"A𠈓C"的索引1处的字符是 代理机器人

要记住的关键点是代理项对表示 < strong > 32位 单个字符。

您可以尝试这个代码,它将返回 True

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

IsSurrogatePair 方法(String,Int32)

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

这在 绳子,长度属性中得到进一步解释:

属性返回 < strong > 数量的 Char 对象 实例,而不是 Unicode 字符的数量。 原因是 Unicode字符可以由多个字符表示 System.Globaly.StringInfo 类使用每个 Unicode 字符而不是每个字符。

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

MSDN 声明

属性返回此 实例,而不是 Unicode 字符数。

但是,如果您真正想知道的是“ text 元素”的数量,而不是 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对象中分割中间的“字母”,并且打印的结果将与字符串不对应。

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

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

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

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

  • 甚至字符串的数量也不等于打印出来的字符串的长度,这取决于字体和其他因素,而且因为有些字符打印出来的时候有很多重叠的字体(字距调整) ,所以一个字符串在屏幕上的长度并不一定等于字符串长度的总和!

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

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

而且,有什么意义呢?为什么这些指标很重要?只有你能回答这个问题,但就我个人而言,我觉得这些都无关紧要。我发现限制数据输入在逻辑上更多地是通过字节限制来实现的,因为无论如何这都是需要传输或存储的。限制显示大小最好由显示端软件完成——如果消息的像素为100,那么字符的数量取决于字体等,而数据层软件无论如何也不知道这一点。最后,考虑到 unicode 标准的复杂性,如果您尝试其他方法,那么无论如何都可能在边缘情况下出现 bug。

因此,这是一个没有很多通用用途的难题。代码单元的数量计算起来很简单——它只是底层数据数组的长度——而且作为一个通用规则,它是最有意义/最有用的,只有一个简单的定义。

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

正如其他人所说,它不是字符串中的字符数,而是 Char 对象的数量。字符是代码点 U + 20213。因为该值在16位字符类型的范围之外,所以它以 UTF-16编码为代理对 D840 DE13

得到字符长度的方法在其他答案中也有提到。但是应该谨慎使用,因为在 Unicode 中有许多表示字符的方法。“ à”可以是1个合成字符或2个字符(a + 变音符)。可能需要像 推特那样进行标准化。

你应该看看这个
绝对最低限度每个软件开发人员绝对,肯定必须知道 Unicode 和字符集(没有借口!)

这是因为 length()仅适用于不大于 U+FFFF的 Unicode 代码点。这组代码点被称为 基本多语言平面(BMP) ,只使用2个字节。

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

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

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

好了,进去。Net 和 C # 将所有字符串编码为 UTF-16LEstring以字符序列的形式存储。每个 char封装了2字节或16位的存储。

我们在纸上或屏幕上看到的单个字母、字符、字形、符号或标点符号可以看作是单个文本元素。如 UNICODE 标准附件 # 29 UNICODE 文本分段所述,每个文本元素由一个或多个代码点表示。代码的详尽列表可以是 在这里发现的

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

根据我们现在所知道的我们可以推断,一个文本元素可以存储为一个 char,作为两个字符的代理对,或者,如果文本元素由多个代码点表示,一些单个字符和代理对的组合。似乎这还不够复杂,一些文本元素可以用 UNICODE 标准附件 # 15,UNICODE 规范化表格描述的代码点的不同组合来表示。


插曲

因此,呈现时看起来相同的字符串实际上可以由不同的字符组合组成。对两个这样的字符串进行有序(逐字节)的比较将检测到差异,这可能是意外的或不希望的。

你可以重新编码。净字符串。使他们使用相同的标准化形式。一旦标准化,具有相同文本元素的两个字符串将以相同的方式进行编码。为此,请使用 绳子,正常化函数。但是,请记住,一些不同的文本元素看起来相似。校对:-S


那么,这一切对于这个问题意味着什么呢?文本元素 '𠈓'由单个代码点 U + 20213中日韩统一表意文字扩展区B表示。这意味着不能将其编码为单个 char,而必须使用两个字符将其编码为代理项对。这就是为什么 string bstring a长一个 char

如果需要可靠地计算 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 标准附件 # 29,“匹配用户感知的目标不能总是完全实现,因为文本本身并不总是包含足够的信息来明确地确定边界。”所述