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

wei*_*i37 145 .net c# string unicode unicode-string

这段代码:

string a = "abc";
string b = "AC";
Console.WriteLine("Length a = {0}", a.Length);
Console.WriteLine("Length b = {0}", b.Length);
Run Code Online (Sandbox Code Playgroud)

输出:

Length a = 3
Length b = 4
Run Code Online (Sandbox Code Playgroud)

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

Ada*_*ppe 232

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

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

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

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

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

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

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

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

因此,这是一个很难通用的问题.代码单元的数量很容易计算 - 它只是底层数据数组的长度 - 并且作为一般规则最有意义/最有用,具有简单的定义.

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

  • "为什么这些指标很重要......" (13认同)
  • 基本上'.Length'并不是大多数人所认为的.也许应该有一组更具体的属性(例如GlyphCount)和标记为过时的长度! (9认同)
  • @locster我同意,但不要认为`Length`应该是过时的,以保持与数组的类比. (8认同)
  • 这不是真的(一个常见的误解) - 使用UTF-32,lengthInBytes/4会给出*代码点*的数量,但这与*"字符"或字形的数量不相同.考虑LATIN SMALL LETTER E,接着是COMBINING DIAERESIS ...打印为单个字符,它甚至可以标准化为单个代码点,但它仍然是两个单位长,即使在UTF-32中也是如此. (4认同)
  • @locster它不应该过时.python一个很有意义,没有人质疑它. (2认同)
  • 我认为 .Length 很有意义,并且是一种自然属性,只要您了解它是什么以及为什么会这样。然后它就像任何其他数组一样工作(在某些语言中,例如 D,就该语言而言,字符串字面上就是一个数组,并且它工作得非常好) (2认同)
  • 只是一点补充:可以有多个重音(或者一般来说,组合字符),例如 ọ̵̌ 或 ɘ̧̊̄ 应该清楚的是,您不能为所有可能的组合预定义 unicode 代码点。 (2认同)

nan*_*nny 61

文件中的String.Length属性:

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

  • Java以相同的方式运行(也为`String b`打印4),因为它在char数组中使用UTF-16表示.它是UTF-8中的4字节字符. (3认同)

Hab*_*bib 32

索引1中的角色"AC"SurrogatePair

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

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

Console.WriteLine(char.IsSurrogatePair("AC", 1));
Run Code Online (Sandbox Code Playgroud)

Char.IsSurrogatePair方法(String,Int32)

true如果s参数包括位置index和index + 1的相邻字符,并且位置索引处字符的数值范围为U + D800到U + DBFF,位置索引+ 1处字符的数值范围为U + DC00到U + DFFF; 否则,false.

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

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


dee*_*see 23

正如其他答案所指出的那样,即使有3个可见字符,它们也用4个char对象表示.这就是Length4而不是3的原因.

MSDN声明

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

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

var si = new StringInfo("AC");
Console.WriteLine(si.LengthInTextElements); // 3
Run Code Online (Sandbox Code Playgroud)

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

var enumerator = StringInfo.GetTextElementEnumerator("AC");
while(enumerator.MoveNext()){
    Console.WriteLine(enumerator.Current);
}
Run Code Online (Sandbox Code Playgroud)

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

  • +1用于阐明`LengthInTextElements` :) (3认同)

Yuv*_*kov 20

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

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


phu*_*clv 10

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

在其他答案中提到了获得字符长度的方法.但是应该谨慎使用,因为可以有很多方法来表示Unicode中的字符."à"可以是1个组合字符或2个字符(a +变音符号).可能需要标准化,就像twitter一样.

您应该阅读
绝对最低限度每个软件开发人员绝对必须知道的Unicode和字符集(没有借口!)


Pie*_*ard 6

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

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

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

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


Jod*_*ell 6

好的,在.Net和C#中,所有字符串都编码为UTF-16LE.A string存储为一系列字符.每个char封装2个字节或16位的存储.

我们在"纸上或屏幕上"看到的单个字母,字符,字形,符号或标点符号可以被视为单个文本元素.如Unicode标准附件#29 UNICODE TEXT SEGMENTATION中所述,每个文本元素由一个或多个代码点表示.可以在此处找到详尽的代码列表.

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

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


插曲

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

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


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

如果你需要可靠(参见警告)计算一个文本元素的数量,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);
Run Code Online (Sandbox Code Playgroud)

给出输出,

"Length a = 3"
"Length b = 3"
Run Code Online (Sandbox Code Playgroud)

正如所料.


警告

StringInfoTextElementEnumerator类中的Unicode文本分段的.Net实现通常应该是有用的,并且在大多数情况下,将产生调用者期望的响应.但是,正如Unicode标准附件#29中所述,"匹配用户感知的目标并不总是能够完全满足,因为单独的文本并不总是包含足够的信息来明确地确定边界."