it-swarm.com.de

Warum ist die Länge dieser Zeichenfolge länger als die Anzahl der darin enthaltenen Zeichen?

Dieser Code:

string a = "abc";
string b = "A????C";
Console.WriteLine("Length a = {0}", a.Length);
Console.WriteLine("Length b = {0}", b.Length);

ausgänge:

Length a = 3
Length b = 4

Warum? Ich kann mir nur vorstellen, dass das chinesische Zeichen 2 Bytes lang ist und die Methode .Length Die Anzahl der Bytes zurückgibt.

144
weini37

Alle anderen geben die oberflächliche Antwort, aber es gibt auch eine tiefere Begründung: Die Anzahl der "Zeichen" ist eine schwer zu definierende Frage und kann überraschend teuer in der Berechnung sein, wohingegen eine Längeneigenschaft schnell sein sollte.

Warum ist es schwierig zu definieren? Nun, es gibt ein paar Optionen und keine ist wirklich gültiger als die andere:

  • Die Anzahl der Codeeinheiten (Bytes oder andere Datenblöcke mit fester Größe; C # und Windows verwenden in der Regel UTF-16, sodass die Anzahl der Zwei-Byte-Teile zurückgegeben wird) ist sicherlich relevant, da der Computer die Daten in dieser Form noch verarbeiten muss für viele Zwecke (das Schreiben in eine Datei zum Beispiel kümmert sich eher um Bytes als um Zeichen)

  • Die Anzahl der Unicode-Codepunkte ist recht einfach zu berechnen (obwohl O(n), weil Sie die Zeichenfolge nach Ersatzpaaren durchsuchen müssen) und möglicherweise für einen Texteditor von Bedeutung ist ... ist dies jedoch nicht Entspricht der Anzahl der auf dem Bildschirm gedruckten Zeichen (sog. Grapheme). Einige Buchstaben mit Akzent können beispielsweise in zwei Formen dargestellt werden: ein einzelner Codepunkt oder zwei miteinander gepaarte Punkte, einer für den Buchstaben, und einer mit der Aufschrift "Hinzufügen" ein Akzent für meinen Partnerbuchstaben ". Wäre das Paar zwei Zeichen oder eins? Sie können Zeichenfolgen normalisieren, um dies zu erleichtern, aber nicht alle gültigen Buchstaben haben eine einzige Codepunktdarstellung.

  • Selbst die Anzahl der Grapheme ist nicht gleich der Länge einer gedruckten Zeichenfolge, die unter anderem von der Schriftart abhängt. Da einige Zeichen in vielen Schriftarten mit einer gewissen Überlappung gedruckt werden (Kerning), entspricht dies nicht der Länge einer Zeichenfolge auf dem Bildschirm ist sowieso nicht unbedingt gleich der Summe der Länge der Grapheme!

  • Einige Unicode-Punkte sind nicht einmal Zeichen im herkömmlichen Sinne, sondern eine Art Kontrollmarker. Wie eine Markierung für die Bytereihenfolge oder eine Anzeige von rechts nach links. Zählen diese?

Kurz gesagt, die Länge eines Strings ist eine lächerlich komplexe Frage, und die Berechnung kann viel CPU-Zeit sowie Datentabellen in Anspruch nehmen.

Darüber hinaus, was ist der Sinn? Warum sind diese Metriken wichtig? Nun, nur Sie können das für Ihren Fall beantworten, aber ich persönlich finde, dass sie im Allgemeinen irrelevant sind. Die Dateneingabe einzuschränken, finde ich, erfolgt logischer durch Bytegrenzen, da dies sowieso übertragen oder gespeichert werden muss. Das Begrenzen der Anzeigegröße wird besser von der Software auf der Anzeigeseite vorgenommen. Wenn Sie 100 Pixel für die Nachricht haben, hängt die Anzahl der Zeichen von der Schriftart usw. ab, die der Datenschichtsoftware ohnehin nicht bekannt ist. Angesichts der Komplexität des Unicode-Standards werden Sie wahrscheinlich ohnehin Fehler in den Edge-Fällen haben, wenn Sie etwas anderes ausprobieren.

Es ist also eine schwierige Frage, die nicht viel für allgemeine Zwecke verwendet wird. Die Anzahl der Codeeinheiten ist trivial zu berechnen - es ist nur die Länge des zugrunde liegenden Datenarrays - und die sinnvollste/nützlichste Regel mit einer einfachen Definition.

Deshalb hat b Länge 4 jenseits der oberflächlichen Erklärung von "weil die Dokumentation es sagt".

232
Adam D. Ruppe

Aus dem Dokumentation des String.Length Eigentum:

Die Length-Eigenschaft gibt die Anzahl der Char -Objekte in dieser Instanz zurück, nicht die Anzahl der Unicode-Zeichen. Der Grund dafür ist, dass ein Unicode-Zeichen möglicherweise durch mehr als ein Char dargestellt wird. Verwenden Sie die Klasse System.Globalization.StringInfo , um mit jedem Unicode-Zeichen statt mit jedem Char zu arbeiten.

61
nanny

Dein Charakter bei Index 1 in "A????C" ist ein SurrogatePair

Der wichtigste Punkt, den Sie sich merken sollten, ist, dass Ersatzpaare 32-Bit-Einzelzeichen darstellen.

Sie können diesen Code ausprobieren und er gibt True zurück.

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

Char.IsSurrogatePair-Methode (String, Int32)

true, wenn der Parameter s benachbarte Zeichen an den Positionen Index und Index + 1 und den numerischen Wert des Zeichens an den Positionen Indexbereichen enthält von U + D800 bis U + DBFF, und der numerische Wert des Zeichens am Positionsindex + 1 reicht von U + DC00 bis U + DFFF; ansonsten false.

Dies wird in der Eigenschaft String.Length näher erläutert:

Die Length-Eigenschaft gibt die Anzahl der Char-Objekte in dieser Instanz und nicht die Anzahl der Unicode-Zeichen zurück. Der Grund dafür ist, dass ein Unicode-Zeichen möglicherweise durch mehrere dargestellt wird als ein Char. Verwenden Sie die System.Globalization.StringInfo-Klasse, um mit jedem Unicode-Zeichen statt mit jedem Zeichen zu arbeiten.

32
Habib

Wie die anderen Antworten gezeigt haben, werden 3 sichtbare Zeichen mit 4 char Objekten dargestellt. Aus diesem Grund ist Length 4 und nicht 3.

MSDN gibt das an

Die Length-Eigenschaft gibt die Anzahl der Char-Objekte in dieser Instanz und nicht die Anzahl der Unicode-Zeichen zurück.

Wenn Sie jedoch wirklich die Anzahl der "Textelemente" und nicht die Anzahl der Char -Objekte wissen möchten, können Sie die Klasse StringInfo verwenden.

var si = new StringInfo("A????C");
Console.WriteLine(si.LengthInTextElements); // 3

Sie können auch jedes Textelement wie folgt aufzählen

var enumerator = StringInfo.GetTextElementEnumerator("A????C");
while(enumerator.MoveNext()){
    Console.WriteLine(enumerator.Current);
}

Wenn Sie foreach für die Zeichenfolge verwenden, wird der mittlere "Buchstabe" in zwei char Objekte aufgeteilt, und das Druckergebnis entspricht nicht der Zeichenfolge.

23
dee-see

Dies liegt daran, dass die Eigenschaft Length die Anzahl der Zeichenobjekte und nicht die Anzahl der Unicode-Zeichen zurückgibt. In Ihrem Fall wird eines der Unicode-Zeichen durch mehr als ein Zeichenobjekt (SurrogatePair) dargestellt.

Die Length-Eigenschaft gibt die Anzahl der Char-Objekte in dieser Instanz und nicht die Anzahl der Unicode-Zeichen zurück. Der Grund dafür ist, dass ein Unicode-Zeichen möglicherweise durch mehrere Zeichen dargestellt wird. Verwenden Sie die System.Globalization.StringInfo-Klasse, um mit jedem Unicode-Zeichen statt mit jedem Zeichen zu arbeiten.

20
Yuval Itzchakov

Wie andere gesagt haben, ist es nicht die Anzahl der Zeichen in der Zeichenfolge, sondern die Anzahl der Char-Objekte. Der Charakter ???? ist der Codepunkt U + 20213. Da der Wert außerhalb des Bereichs des 16-Bit-Zeichentyps liegt, wird er in UTF-16 als Ersatzpaar D840 DE13 Codiert.

Die Art und Weise, wie die Länge in Zeichen ermittelt werden kann, wurde in den anderen Antworten erwähnt. Es sollte jedoch mit Vorsicht verwendet werden, da es viele Möglichkeiten gibt, ein Zeichen in Unicode darzustellen. "à" kann 1 zusammengesetztes Zeichen oder 2 Zeichen (a + diakritische Zeichen) sein. Möglicherweise ist eine Normalisierung erforderlich, wie im Fall von Twitter .

Das solltest du lesen
Das absolute Minimum, das jeder Softwareentwickler unbedingt über Unicode und Zeichensätze wissen muss (keine Ausreden!)

10
phuclv

Okay, in .Net und C # werden alle Zeichenfolgen als TF-16LE codiert. Ein string wird als Folge von Zeichen gespeichert. Jedes char kapselt die Speicherung von 2 Bytes oder 16 Bits.

Was wir "auf Papier oder Bildschirm" als einen einzelnen Buchstaben, ein Zeichen, eine Glyphe, ein Symbol oder ein Satzzeichen sehen, kann als ein einzelnes Textelement betrachtet werden. Wie in nicode Standard Annex # 29 UNICODE TEXT SEGMENTATION beschrieben, wird jedes Textelement durch einen oder mehrere Codepunkte dargestellt. Eine vollständige Liste der Codes finden Sie unter hier zu finden .

Jeder Codepunkt muss für die interne Darstellung durch einen Computer binär codiert werden. Wie bereits erwähnt, speichert jedes char 2 Bytes. Codepunkte bei oder unter U+FFFF kann in einem einzigen char gespeichert werden. Codepunkte über U+FFFF werden als Ersatzpaar gespeichert, wobei zwei Zeichen verwendet werden, um einen einzelnen Codepunkt darzustellen.

Wenn wir wissen, was wir daraus ableiten können, kann ein Textelement als ein char , als Ersatzpaar aus zwei Zeichen oder, wenn das Textelement durch mehrere Codepunkte dargestellt wird, gespeichert werden eine Kombination aus einzelnen Zeichen und Ersatzpaaren. Als ob das nicht kompliziert genug wäre, können einige Textelemente durch verschiedene Kombinationen von Codepunkten dargestellt werden, wie beschrieben in Unicode-Standard, Anhang Nr. 15, UNICODE NORMALIZATION FORMS .


Zwischenspiel

Zeichenfolgen, die beim Rendern gleich aussehen, können also auch aus einer anderen Zeichenkombination bestehen. Ein ordinaler (byteweiser) Vergleich zweier solcher Zeichenfolgen würde einen Unterschied feststellen. Dies kann unerwartet oder unerwünscht sein.

Sie können .Net-Zeichenfolgen neu codieren. damit sie das gleiche Normalisierungsformular verwenden. Nach dem Normalisieren werden zwei Zeichenfolgen mit denselben Textelementen auf dieselbe Weise codiert. Verwenden Sie dazu die Funktion string.Normalize . Beachten Sie jedoch, dass einige unterschiedliche Textelemente ähnlich aussehen. : -s


Also, was bedeutet das alles in Bezug auf die Frage? Das Textelement '????' wird durch den einzelnen Code Point U + 20213 cjk unified ideographs extension b dargestellt. Dies bedeutet, dass es nicht als einzelnes char codiert werden kann und als Ersatzpaar mit zwei Zeichen codiert werden muss. Deshalb string b ist eins char länger als string a.

Wenn Sie die Anzahl der Textelemente in einem string zuverlässig (siehe Einschränkung) zählen müssen, sollten Sie das System.Globalization.StringInfo Klasse wie diese.

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);

ausgabe geben,

"Length a = 3"
"Length b = 3"

wie erwartet.


Einschränkung

Die .Net-Implementierung der Unicode-Textsegmentierung in den Klassen StringInfo und TextElementEnumerator sollte im Allgemeinen nützlich sein und in den meisten Fällen zu Ergebnissen führen eine Antwort, die der Anrufer erwartet. Wie in nicode Standard Annex # 29, "Das Ziel des Übereinstimmens von Benutzerwahrnehmungen kann nicht immer genau erreicht werden, da der Text allein nicht immer genug Informationen enthält, um Grenzen eindeutig zu bestimmen."

6
Jodrell

Dies liegt daran, dass length() nur für Unicode-Codepunkte funktioniert, die nicht größer als U+FFFF Sind. Diese Gruppe von Codepunkten wird als Basic Multilingual Plane (BMP) bezeichnet und verwendet nur 2 Bytes.

Unicode-Codepunkte außerhalb von BMP werden in UTF-16 mit 4-Byte-Ersatzpaaren dargestellt.

Verwenden Sie StringInfo, um die Anzahl der Zeichen (3) korrekt zu zählen.

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