この文字列の長さが文字数よりも長いのはなぜですか?


145

このコード:

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メソッドがバイト数を返すことです。


10
タイトルを見ただけで、サロゲートペアの問題であることがわかりました。ああ、良い 'ol System.Globalizationはあなたの味方です!
Chris Cirefice 2014年

9
UTF-16では2バイトではなく4バイト長
phuclv '18年

charの10進数値𠈓は131603であり、charsは符号なしバイトであるため、4ではなく2文字でその値を達成できる(符号なし16ビット値の最大は65535(または65536のバリエーション)であり、2文字を使用して表すことができることを意味します) 65536 * 2(131072)ではなく65536 * 65536のバリエーション(4,294,967,296、事実上32ビット値)の最大バリエーション数
GMasucci 14年

3
@GMAsucci:それはUTF16の文字のサイズが2バイトであるため、UTF16で2つの文字が、4バイトですが、それ以外の場合は、65536件のバリエーションを保存することができませんでしたが、唯一の256
Kaiserludi

4
私は「Unicodeとキャラクタセットについて知っておくべき、絶対に絶対最小すべてのソフトウェア開発(言い訳!)」偉大な記事を読んでお勧めしますjoelonsoftware.com/articles/Unicode.html
ItsMe

回答:


232

他の誰もが表面的な答えを出しているが、より深い根拠もある。「文字」の数は定義するのが難しい質問であり、計算するのに驚くほど高くつく可能性があるが、長さのプロパティは高速でなければならない。

なぜ定義が難しいのですか?まあ、いくつかのオプションがあり、どれも他のものよりも本当に有効なものはありません:

  • コードユニットの数(バイトまたはその他の固定サイズのデータ​​チャンク。C#およびWindowsは通常UTF-16を使用するため、2バイトのピースの数を返します)は、コンピューターがその形式でデータを処理する必要があるため、確かに適切です。多くの目的(たとえば、ファイルへの書き込みでは、文字ではなくバイトが重要)

  • Unicodeコードポイントの数はかなり計算しやすく(サロゲートペアの文字列をスキャンする必要があるためO(n)ですが)、テキストエディターにとって重要かもしれませんが、実際には文字数と同じではありません。画面に印刷されます(書記素と呼ばれます)。たとえば、いくつかのアクセント付き文字は、2つの形式で表すことができます。1つのコードポイント、またはペアになっている2つのポイントで、1つは文字を表し、もう1つは「アクセントをパートナーの文字に追加する」と言います。ペアは2文字ですか、1文字ですか?これを助けるために文字列を正規化できますが、すべての有効な文字が単一のコードポイント表現を持っているわけではありません。

  • 書記素の数でさえ、印刷された文字列の長さと同じではありません。これは他の要因の中でフォントに依存します。また、一部の文字は多くのフォント(カーニング)で重複して印刷されるため、画面上の文字列の長さとにかく、書記素の長さの合計と必ずしも同じではありません!

  • 一部のUnicodeポイントは、従来の意味での文字ではなく、ある種のコントロールマーカーです。バイトオーダーマーカーまたは右から左へのインジケーターのように。これらは重要ですか?

要するに、文字列の長さは実際には途方もなく複雑な問題であり、その計算にはデータテーブルだけでなく多くのCPU時間もかかる可能性があります。

また、ポイントは何ですか?これらの指標が重要なのはなぜですか?ええと、あなただけがあなたのケースでそれに答えることができますが、個人的には、それらは一般的に無関係であると私は思います。私が見つけたデータエントリの制限は、バイト制限によってより論理的に行われます。これは、転送または保存する必要があるためです。ディスプレイサイズの制限は、ディスプレイ側のソフトウェアで行うのが適切です。メッセージに100ピクセルある場合、どのくらいの文字数に収まるかは、フォントなどによって異なります。これは、データレイヤーソフトウェアでは不明です。最後に、Unicode標準の複雑さを考えると、他のことを試した場合でも、とにかくエッジケースでバグが発生することになります。

したがって、汎用的な使用法があまりないという難しい質問です。コード単位の数を計算するのは簡単です。これは、基礎となるデータ配列の長さです-単純な定義で、一般的なルールとして最も意味のある/便利なものです。

それが、「ドキュメントがそう言っているから」という表面的な説明を超えるb長さがある理由4です。


9
基本的に、「。Length」は、ほとんどのプログラマーが考えているものではありません。たぶん、より具体的なプロパティ(GlyphCountなど)と、LengthとしてマークされたLengthのセットがあるはずです!
redcalx 2014年

8
@locster同意するが、Length配列との類似性を維持するために、時代遅れであるとは思わない。
Kroltan 2014年

2
@locster古くなってはいけません。Pythonの1つは多くの意味があり、誰もそれに疑問を投げかけません。
simonzack 2014年

1
.Lengthは、それが何であり、なぜそのようになっているのかを理解している限り、多くの意味があり、自然な特性だと思います。その後、他の配列と同じように機能します(Dなどの一部の言語では、文字列は文字通り、言語に関する限り配列であり、非常にうまく機能します)
Adam D. Ruppe 14年

4
すなわち、(誤解)真実ではない- UTF-32と、lengthInBytes / 4の数与えるコードポイントを、それはない「文字」または書記素の数と同じ。単一の文字として出力されるラテン小文字Eの後に結合ダイアレシスが続くことを考えてください。単一のコードポイントに正規化することもできますが、UTF-32でも2単位の長さです。
アダムD.ルッペ

62

ドキュメントString.Lengthプロパティ:

Lengthプロパティは、Unicode文字の数ではなく、このインスタンスのCharオブジェクトの数を返します。その理由は、Unicode文字が複数のCharによって表される可能性があるためです。System.Globalization.StringInfoクラスを使用して、各Charではなく各Unicode文字を処理します。


3
Javaは、String bchar配列でUTF-16表現を使用するため、同じように動作します(これも4を出力します)。UTF-8の4バイト文字です。
マイケル

32

インデックス1のあなたのキャラクター"A𠈓C"SurrogatePairです

覚えておくべき重要な点は、サロゲートペアは32ビットの 単一文字を表すということです。

あなたはこのコードを試すことができ、それは戻ります True

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

Char.IsSurrogatePairメソッド(String、Int32)

trueindexパラメータとindex + 1の位置隣接する文字がsパラメータに含まれ、位置indexの文字の数値がU + D800からU + DBFFの範囲で、位置index + 1の文字の数値がUの範囲である場合+ DC00からU + DFFFまで。それ以外の場合false

これは、String.Lengthプロパティでさらに説明されてます。

Lengthプロパティは、Unicode文字の数ではなく、このインスタンスのCharオブジェクト数を返しますその理由は、Unicode文字が複数のCharによって表される可能性があるためです。System.Globalization.StringInfoクラスを使用して、各Charではなく各Unicode文字を処理します。


24

他の答えが指摘しているように、3つの表示文字がある場合でも、それらは4つのcharオブジェクトで表されます。これが、Length3ではなく4である理由です。

MSDNによると

Lengthプロパティは、Unicode文字の数ではなく、このインスタンスのCharオブジェクトの数を返します。

ただし、本当に知りたいのが「テキスト要素」の数であり、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文字列で使用すると、中央の「文字」が2つのcharオブジェクトに分割され、印刷結果は文字列に対応しなくなります。


20

これは、LengthプロパティがUnicode オブジェクトの数ではなくcharオブジェクトの数を返すためです。あなたの場合、Unicode文字の1つが複数のcharオブジェクト(SurrogatePair)で表されています。

Lengthプロパティは、Unicode文字の数ではなく、このインスタンスのCharオブジェクトの数を返します。その理由は、Unicode文字が複数のCharによって表される可能性があるためです。System.Globalization.StringInfoクラスを使用して、各Charではなく各Unicode文字を処理します。


1
この回答では「文字」の使用があいまいです。少なくとも最初の用語を正確な用語に置き換えることをお勧めします。
2014

1
ありがとうございました。あいまいさを修正しました。
Yuval Itzchakov 2014年

10

他の人が言ったように、それは文字列の文字数ではなく、Charオブジェクトの数です。文字はコードポイントU + 20213です。値は16ビットのchar型の範囲外であるため、サロゲートペアとしてUTF-16でエンコードされD840 DE13ます。

文字の長さを取得する方法は、他の回答で言及されました。ただし、Unicodeで文字を表現するには多くの方法があるため、注意して使用する必要があります。「à」は、1つの合成文字または2つの文字(a +分音記号)です。ツイッターのように正規化が必要な場合があります。

あなたはこれを絶対に読んでください
すべてのソフトウェア開発者は絶対に、積極的にUnicodeと文字セットについて知っておく必要があります(言い訳はありません!)


6

これは、をlength()超えないUnicodeコードポイントに対してのみ機能するためですU+FFFF。このコードポイントのセットは、Basic Multilingual Plane(BMP)と呼ばれ、2バイトのみを使用します。

外のUnicodeコードポイントは、BMP4バイトのサロゲートペアを使用してUTF-16で表されます。

文字数(3)を正しく数えるには、 StringInfo

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

6

さて、.NetとC#では、すべての文字列はUTF-16LEとしてエンコードされます。A stringは文字のシーケンスとして格納されます。それぞれcharが2バイトまたは16ビットのストレージをカプセル化します。

「紙または画面上」で単一の文字、文字、グリフ、記号、または句読点として見えるものは、単一のテキスト要素と考えることができます。Unicode Standard Annex#29 UNICODE TEXT SEGMENTATIONで説明されているように、各テキスト要素は1つ以上のコードポイントで表されます。コードの完全なリストはここにあります

各コードポイントは、コンピューターによる内部表現のためにバイナリにエンコードする必要があります。前述のように、それぞれにchar2バイトが格納されます。以下のコードポイントを1つのU+FFFFに保存できますchar。上記のコードポイントU+FFFFは、2つの文字を使用して単一のコードポイントを表すサロゲートペアとして保存されます。

推測できることがわかったので、テキスト要素は1つcharとして、2つの文字のサロゲートペアとして、またはテキスト要素が複数のコードポイントで表される場合、単一の文字とサロゲートペアのいくつかの組み合わせとして格納できます。それが十分に複雑ではないかのように、一部のテキスト要素は、Unicode Standard Annex#15、UNICODE NORMALIZATION FORMSで説明さているように、コードポイントのさまざまな組み合わせで表すことができます。


間奏

したがって、レンダリングされたときに同じに見える文字列は、実際には異なる文字の組み合わせで構成されている可能性があります。そのような2つの文字列の序数(バイトごと)比較は違いを検出しますが、これは予期しないものであるか望ましくない場合があります。

.Net文字列を再エンコードできます。同じ正規化フォームを使用するようにします。正規化されると、同じテキスト要素を持つ2つの文字列は同じ方法でエンコードされます。これを行うには、string.Normalize関数を使用します。ただし、いくつかの異なるテキスト要素は互いに似ていることに注意してください。:-s


それで、これは質問に関してどのような意味がありますか?テキスト要素'𠈓'は、単一のコードポイントU + 20213 cjk統一表意文字拡張bで表されます。つまり、1 charつの文字としてエンコードすることはできず、2つの文字を使用してサロゲートペアとしてエンコードする必要があります。これがstring b1つchar長い理由ですstring a

確実に(警告を参照)必要があるstring場合は、a内のテキスト要素の数をカウントする場合は、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"

予想通り。


警告

StringInfoおよびTextElementEnumeratorクラスのUnicodeテキストセグメンテーションの.Net実装は、一般的に有用であり、ほとんどの場合、呼び出し元が期待する応答を生成します。ただし、Unicode Standard Annex#29に記載されているように、「テキストだけでは境界を明確に決定するのに十分な情報が常に含まれているとは限らないため、ユーザーの認識を一致させる目標は常に正確に満たされるとは限りません。」


あなたの答えは混乱する可能性があると思います。この場合、𠈓は単一のコードポイントですが、そのコードポイントは0xFFFFを超えるため、サロゲートペアを使用して2つのコード単位として表す必要があります。書記素は、コードポイントの上に構築された別の概念です。書記素は、韓国語のハングルや多くのラテン語ベースの言語で見られるように、単一のコードポイントまたは複数のコードポイントで表すことができます。
nhahtdh 2014年

@nhahtdh、同意する、私の答えは間違っていた。私はそれを書き直しました。うまくいけば、より明確になりました。
Jodrell 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.