Pourquoi la longueur de cette chaîne est-elle plus longue que le nombre de caractères qu'elle contient?


145

Ce code:

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

les sorties:

Length a = 3
Length b = 4

Pourquoi? La seule chose que je pourrais imaginer est que le caractère chinois fait 2 octets et que la .Lengthméthode renvoie le nombre d'octets.


10
Comment ai-je su que c'était un problème de paire de substitution rien qu'en regardant le titre. Ah, bon vieux système, la mondialisation est votre alliée!
Chris Cirefice

9
il fait 4 octets de long en UTF-16, pas 2
phuclv

la valeur décimale du caractère 𠈓est 131603, et comme les caractères sont des octets non signés, cela signifie que vous pouvez obtenir cette valeur en 2 caractères au lieu de 4 (la valeur maximale de 16 bits non signée est de 65535 (ou 65536 variations) et l'utilisation de 2 caractères pour le représenter permet pour un nombre maximum de variations non pas de 65536 * 2 (131072) mais plutôt de 65536 * 65536 variations (4 294 967 296, effectivement une valeur de 32 bits)
GMasucci

3
@GMAsucci: C'est 2 caractères en UTF-16, mais 4 octets, car un caractère UTF16 a une taille de 2 octets, sinon il ne pourrait pas stocker 65536 variations, mais seulement 256.
Kaiserludi

4
Je recommande de lire le grand article `` Le minimum absolu que chaque développeur de logiciel doit absolument, positivement savoir sur Unicode et les jeux de caractères (sans excuses!) '' Joelonsoftware.com/articles/Unicode.html
ItsMe

Réponses:


232

Tout le monde donne la réponse superficielle, mais il y a aussi une justification plus profonde: le nombre de "caractères" est une question difficile à définir et peut être étonnamment coûteux à calculer, alors qu'une propriété de longueur devrait être rapide.

Pourquoi est-ce difficile à définir? Eh bien, il y a quelques options et aucune n'est vraiment plus valable qu'une autre:

  • Le nombre d'unités de code (octets ou autre bloc de données de taille fixe; C # et Windows utilisent généralement UTF-16, il renvoie donc le nombre de morceaux de deux octets) est certainement pertinent, car l'ordinateur doit toujours traiter les données sous cette forme à de nombreuses fins (l'écriture dans un fichier, par exemple, se soucie des octets plutôt que des caractères)

  • Le nombre de points de code Unicode est assez facile à calculer (bien que O (n) car vous devez scanner la chaîne à la recherche de paires de substitution) et peut être important pour un éditeur de texte ... mais n'est pas en fait la même chose que le nombre de caractères imprimés à l'écran (appelés graphèmes). Par exemple, certaines lettres accentuées peuvent être représentées sous deux formes: un seul point de code ou deux points appariés, l'un représentant la lettre et l'autre disant "ajouter un accent à la lettre de mon partenaire". La paire serait-elle composée de deux personnages ou d'un seul? Vous pouvez normaliser les chaînes pour vous aider, mais toutes les lettres valides n'ont pas une seule représentation de point de code.

  • Même le nombre de graphèmes n'est pas le même que la longueur d'une chaîne imprimée, qui dépend de la police entre autres facteurs, et comme certains caractères sont imprimés avec un certain chevauchement dans de nombreuses polices (crénage), la longueur d'une chaîne à l'écran n'est pas forcément égale à la somme de la longueur des graphèmes de toute façon!

  • Certains points Unicode ne sont même pas des caractères au sens traditionnel du terme, mais plutôt une sorte de marqueur de contrôle. Comme un marqueur d'ordre d'octet ou un indicateur de droite à gauche. Cela compte-t-il?

En bref, la longueur d'une chaîne est en fait une question ridiculement complexe et son calcul peut prendre beaucoup de temps CPU ainsi que des tableaux de données.

De plus, à quoi ça sert? Pourquoi ces paramètres sont-ils importants? Eh bien, vous seul pouvez répondre à votre cas, mais personnellement, je trouve qu'ils ne sont généralement pas pertinents. Je trouve que la limitation de la saisie de données est plus logiquement effectuée par des limites d'octets, car c'est ce qui doit être transféré ou stocké de toute façon. La limitation de la taille d'affichage est mieux effectuée par le logiciel côté affichage - si vous avez 100 pixels pour le message, le nombre de caractères que vous ajustez dépend de la police, etc., ce qui n'est de toute façon pas connu par le logiciel de couche de données. Enfin, étant donné la complexité de la norme unicode, vous allez probablement avoir des bugs aux limites des cas de toute façon si vous essayez autre chose.

C'est donc une question difficile avec peu d'utilisation générale. Le nombre d'unités de code est simple à calculer - c'est juste la longueur du tableau de données sous-jacent - et le plus significatif / utile en règle générale, avec une définition simple.

C'est pourquoi la blongueur 4dépasse l'explication superficielle de «parce que la documentation le dit».


9
Essentiellement, «.Length» n'est pas ce que la plupart des codeurs pensent que c'est. Peut-être qu'il devrait y avoir un ensemble de propriétés plus spécifiques (par exemple, GlyphCount) et Length marqués comme obsolètes!
redcalx

8
@locster Je suis d'accord, mais je ne pense pas que cela Lengthdevrait être obsolète, pour maintenir l'analogie avec les tableaux.
Kroltan

2
@locster Cela ne devrait pas être obsolète. Le python a beaucoup de sens et personne ne le remet en question.
simonzack

1
Je pense que la longueur a beaucoup de sens et est une propriété naturelle, tant que vous comprenez ce que c'est et pourquoi il en est ainsi. Ensuite, cela fonctionne comme n'importe quel autre tableau (dans certaines langues comme D, une chaîne est littéralement un tableau en ce qui concerne la langue et cela fonctionne vraiment bien)
Adam D. Ruppe

4
Ce n'est pas vrai (une idée fausse courante) - avec UTF-32, lengthInBytes / 4 donnerait le nombre de points de code , mais ce n'est pas le même que le nombre de "caractères" ou de graphèmes. Considérez la LETTRE MINUSCULE LATINE E suivie d'un DIAGRÈSE DE COMBINAISON ... qui s'imprime comme un seul caractère, il peut même être normalisé en un seul point de code, mais il est toujours long de deux unités, même en UTF-32.
Adam D. Ruppe

62

De la documentation de la String.Lengthpropriété:

La propriété Length renvoie le nombre d' objets Char dans cette instance, et non le nombre de caractères Unicode. La raison en est qu'un caractère Unicode peut être représenté par plusieurs Char . Utilisez la classe System.Globalization.StringInfo pour travailler avec chaque caractère Unicode au lieu de chaque Char .


3
Java se comporte de la même manière (imprimant également 4 pour String b), car il utilise la représentation UTF-16 dans les tableaux de caractères. C'est un caractère de 4 octets en UTF-8.
Michael

32

Votre personnage à l'index 1 dans "A𠈓C"est une paire de substitution

Le point clé à retenir est que les paires de substitution représentent des caractères uniques de 32 bits .

Vous pouvez essayer ce code et il reviendra True

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

Char.IsSurrogatePair, méthode (String, Int32)

truesi le paramètre s comprend des caractères adjacents aux positions index et index + 1 , et que la valeur numérique du caractère à l'index de position va de U + D800 à U + DBFF, et que la valeur numérique du caractère à l'index de position + 1 va de U + DC00 à U + DFFF; autrement, false.

Ceci est expliqué plus en détail dans la propriété String.Length :

La propriété Length renvoie le nombre d'objets Char dans cette instance, et non le nombre de caractères Unicode. La raison en est qu'un caractère Unicode peut être représenté par plusieurs Char. Utilisez la classe System.Globalization.StringInfo pour travailler avec chaque caractère Unicode au lieu de chaque Char.


24

Comme les autres réponses l'ont souligné, même s'il y a 3 caractères visibles, ils sont représentés avec 4 charobjets. C'est pourquoi le Lengthvaut 4 et non 3.

MSDN déclare que

La propriété Length renvoie le nombre d'objets Char dans cette instance, et non le nombre de caractères Unicode.

Cependant, si vous voulez vraiment savoir le nombre d '"éléments de texte" et non le nombre d' Charobjets, vous pouvez utiliser la StringInfoclasse.

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

Vous pouvez également énumérer chaque élément de texte comme ceci

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

Utiliser foreachsur la chaîne divisera la "lettre" du milieu en deux charobjets et le résultat imprimé ne correspondra pas à la chaîne.


20

En effet, la Lengthpropriété renvoie le nombre d' objets char et non le nombre de caractères Unicode. Dans votre cas, l'un des caractères Unicode est représenté par plus d'un objet char (SurrogatePair).

La propriété Length renvoie le nombre d'objets Char dans cette instance, et non le nombre de caractères Unicode. La raison en est qu'un caractère Unicode peut être représenté par plusieurs Char. Utilisez la classe System.Globalization.StringInfo pour travailler avec chaque caractère Unicode au lieu de chaque Char.


1
Vous avez une utilisation ambiguë de «caractère» dans cette réponse. Je suggère de remplacer au moins le premier par une terminologie précise.
Courses de légèreté en orbite

1
Je vous remercie. Correction de l'ambiguïté.
Yuval Itzchakov

10

Comme d'autres l'ont dit, ce n'est pas le nombre de caractères dans la chaîne mais le nombre d'objets Char. Le caractère 𠈓 est le point de code U + 20213. Étant donné que la valeur est en dehors de la plage du type char 16 bits, elle est codée en UTF-16 en tant que paire de substitution D840 DE13.

La manière d'obtenir la longueur en caractères a été mentionnée dans les autres réponses. Cependant, il doit être utilisé avec précaution car il peut y avoir de nombreuses façons de représenter un caractère en Unicode. "à" peut être composé de 1 caractère composé ou de 2 caractères (a + diacritiques). Une normalisation peut être nécessaire comme dans le cas de Twitter .

Vous devriez lire ceci
Le minimum absolu que tout développeur de logiciel doit absolument, positivement savoir sur Unicode et les jeux de caractères (sans excuses!)


6

En effet, cela length()ne fonctionne que pour les points de code Unicode qui ne sont pas plus grands que U+FFFF. Cet ensemble de points de code est connu sous le nom de plan multilingue de base (BMP) et n'utilise que 2 octets.

Les points de code Unicode en dehors de BMPsont représentés en UTF-16 à l'aide de paires de substitution de 4 octets.

Pour compter correctement le nombre de caractères (3), utilisez StringInfo

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

6

D'accord, en .Net et C # toutes les chaînes sont encodées en UTF-16LE . A stringest stocké sous la forme d'une séquence de caractères. Chacun charencapsule le stockage de 2 octets ou 16 bits.

Ce que nous voyons «sur papier ou écran» comme une seule lettre, caractère, glyphe, symbole ou signe de ponctuation peut être considéré comme un élément de texte unique. Comme décrit dans l' annexe 29 de la norme Unicode SEGMENTATION DE TEXTE UNICODE , chaque élément de texte est représenté par un ou plusieurs points de code. Une liste exhaustive des codes peut être trouvée ici .

Chaque point de code doit être encodé en binaire pour une représentation interne par un ordinateur. Comme indiqué, chacun charstocke 2 octets. Les points de code à ou en dessous U+FFFFpeuvent être stockés dans un seul char. Les points de code ci U+FFFF- dessus sont stockés en tant que paire de substitution, en utilisant deux caractères pour représenter un seul point de code.

Compte tenu de ce que nous savons maintenant que nous pouvons déduire, un élément de texte peut être stocké comme un seul char, comme une paire de substitution de deux caractères ou, si l'élément de texte est représenté par plusieurs points de code, une combinaison de caractères uniques et de paires de substitution. Comme si cela n'était pas assez compliqué, certains éléments de texte peuvent être représentés par différentes combinaisons de points de code comme décrit dans l'Annexe n ° 15 de la norme Unicode, FORMULAIRES DE NORMALISATION UNICODE .


Interlude

Ainsi, les chaînes qui se ressemblent lors du rendu peuvent en fait être constituées d'une combinaison différente de caractères. Une comparaison ordinale (octet par octet) de deux de ces chaînes détecterait une différence, cela peut être inattendu ou indésirable.

Vous pouvez ré-encoder les chaînes .Net. afin qu'ils utilisent le même formulaire de normalisation. Une fois normalisées, deux chaînes avec les mêmes éléments de texte seront encodées de la même manière. Pour ce faire, utilisez la fonction string.Normalize . Cependant, rappelez-vous que certains éléments de texte différents se ressemblent. : -s


Alors, qu'est-ce que tout cela signifie par rapport à la question? L'élément de texte '𠈓'est représenté par l' extension d'idéographes unifiés de point de code U + 20213 cjk b . Cela signifie qu'il ne peut pas être codé comme un seul charet doit être codé comme paire de substitution, en utilisant deux caractères. C'est pourquoi il string by en a un charplus long string a.

Si vous avez besoin de compter de manière fiable (voir la mise en garde) le nombre d'éléments de texte dans un, stringvous devez utiliser la System.Globalization.StringInfoclasse comme ceci.

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

donnant la sortie,

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

comme prévu.


Caveat

L'implémentation .Net de la segmentation de texte Unicode dans les classes StringInfoet TextElementEnumeratordevrait être généralement utile et, dans la plupart des cas, produira une réponse attendue par l'appelant. Cependant, comme indiqué dans l' annexe 29 de la norme Unicode, "l'objectif de faire correspondre les perceptions des utilisateurs ne peut pas toujours être atteint exactement parce que le texte seul ne contient pas toujours suffisamment d'informations pour décider sans ambiguïté des limites."


Je pense que votre réponse est potentiellement déroutante. Dans ce cas, 𠈓 n'est qu'un seul point de code, mais puisque son point de code dépasse 0xFFFF, il doit être représenté comme 2 unités de code en utilisant une paire de substitution. Le graphème est un autre concept construit au-dessus du point de code, où un graphème peut être représenté par un seul point de code ou plusieurs points de code, comme on le voit dans le Hangul coréen ou dans de nombreuses langues latines.
nhahtdh

@nhahtdh, je suis d'accord, ma réponse était erronée. Je l'ai réécrit et j'espère qu'il crée maintenant une plus grande clarté.
Jodrell
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.