Perché la lunghezza di questa stringa è più lunga del numero di caratteri in essa contenuti?


145

Questo codice:

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

uscite:

Length a = 3
Length b = 4

Perché? L'unica cosa che potrei immaginare è che il carattere cinese è lungo 2 byte e che il .Lengthmetodo restituisce il conteggio dei byte.


10
Come facevo a sapere che si trattava di un problema di coppia surrogata solo guardando il titolo. Ah, buon sistema. La globalizzazione è il tuo alleato!
Chris Cirefice,

9
è lungo 4 byte in UTF-16, non 2
phuclv il

il valore decimale del carattere 𠈓è 131603 e poiché i caratteri sono byte senza segno, ciò significa che è possibile ottenere quel valore in 2 caratteri anziché 4 (il valore massimo di 16 bit senza segno è 65535 (o 65536 variazioni) e l'utilizzo di 2 caratteri per rappresentarlo consente per un numero massimo di variazioni non di 65536 * 2 (131072) ma piuttosto di 65536 * 65536 variazioni (4.294.967.296, in effetti un valore di 32 bit)
GMasucci,

3
@GMAsucci: sono 2 caratteri in UTF-16, ma 4 byte, perché un carattere UTF16 ha una dimensione di 2 byte, altrimenti non è possibile memorizzare 65536 varianti, ma solo 256.
Kaiserludi

4
Consiglio di leggere il fantastico articolo "Il minimo assoluto che ogni sviluppatore di software deve assolutamente conoscere positivamente, Unicode e set di caratteri (senza scuse!)" Joelonsoftware.com/articles/Unicode.html
È il

Risposte:


232

Tutti gli altri stanno dando la risposta in superficie, ma c'è anche una logica più profonda: il numero di "personaggi" è una domanda difficile da definire e può essere sorprendentemente costoso da calcolare, mentre una proprietà di lunghezza dovrebbe essere veloce.

Perché è difficile da definire? Bene, ci sono alcune opzioni e nessuna è davvero più valida di un'altra:

  • Il numero di unità di codice (byte o altri blocchi di dati di dimensioni fisse; C # e Windows in genere utilizzano UTF-16, quindi restituisce il numero di pezzi a due byte) è certamente rilevante, poiché il computer deve ancora gestire i dati in quella forma per molti scopi (la scrittura in un file, ad esempio, si preoccupa dei byte anziché dei caratteri)

  • Il numero di punti di codice Unicode è abbastanza facile da calcolare (anche se O (n) perché devi scansionare la stringa per coppie surrogate) e potrebbe essere importante per un editor di testo .... ma in realtà non è la stessa cosa del numero di caratteri stampato sullo schermo (chiamato grafemi). Ad esempio, alcune lettere accentate possono essere rappresentate in due forme: un singolo punto di codice o due punti accoppiati insieme, uno che rappresenta la lettera e uno che dice "aggiungi un accento alla lettera del mio partner". La coppia sarebbe composta da due personaggi o uno? È possibile normalizzare le stringhe per facilitare questa operazione, ma non tutte le lettere valide hanno un'unica rappresentazione di punti di codice.

  • Anche il numero di grafemi non è uguale alla lunghezza di una stringa stampata, che dipende dal carattere tra gli altri fattori, e poiché alcuni caratteri sono stampati con una certa sovrapposizione in molti caratteri (crenatura), la lunghezza di una stringa sullo schermo non è necessariamente uguale alla somma della lunghezza dei grafemi!

  • Alcuni punti Unicode non sono nemmeno personaggi nel senso tradizionale, ma piuttosto una sorta di indicatore di controllo. Come un indicatore dell'ordine dei byte o un indicatore da destra a sinistra. Contano questi?

In breve, la lunghezza di una stringa è in realtà una domanda assurdamente complessa e il suo calcolo può richiedere molto tempo CPU e tabelle di dati.

Inoltre, qual è il punto? Perché sono importanti queste metriche? Bene, solo tu puoi rispondere per il tuo caso, ma personalmente trovo che siano generalmente irrilevanti. Limitare l'immissione dei dati che trovo sia più logicamente fatto dai limiti di byte, poiché è ciò che deve essere comunque trasferito o archiviato. Limitare le dimensioni dello schermo è meglio eseguito dal software lato display: se hai 100 pixel per il messaggio, quanti caratteri si adattano dipendono dal carattere, ecc., Che comunque non è noto dal software del livello dati. Infine, data la complessità dello standard unicode, probabilmente avrai comunque dei bug nei casi limite se provi qualcos'altro.

Quindi è una domanda difficile con un uso non generalizzato. Il numero di unità di codice è banale da calcolare - è solo la lunghezza dell'array di dati sottostante - e il più significativo / utile come regola generale, con una definizione semplice.

Ecco perché bha una lunghezza che va 4oltre la spiegazione superficiale di "perché la documentazione dice così".


9
Essenzialmente ".Length" non è ciò che la maggior parte dei programmatori pensa che sia. Forse ci dovrebbe essere un insieme di proprietà più specifiche (ad esempio GlyphCount) e Lunghezza contrassegnate come Obsolete!
redcalx,

8
@locster Sono d'accordo, ma non credo che Lengthdovrebbe essere obsoleto, per mantenere l'analogia con le matrici.
Kroltan,

2
@locster Non dovrebbe essere obsoleto. Il pitone ha molto senso e nessuno lo mette in discussione.
simonzack

1
Penso che la lunghezza abbia molto senso ed è una proprietà naturale, purché tu capisca di cosa si tratta e perché sia ​​così. Quindi funziona come qualsiasi altro array (in alcune lingue come D, una stringa è letteralmente un array per quanto riguarda la lingua e funziona davvero bene)
Adam D. Ruppe

4
Questo non è vero (un malinteso comune) - con UTF-32, lengthInBytes / 4 darebbe il numero di punti di codice , ma non è lo stesso del numero di "caratteri" o grafemi. Considera LETTERA LATINA PICCOLA E seguita da una DIAERESI COMBINATA ... che stampa come un singolo carattere, può anche essere normalizzata in un singolo punto di codice, ma è ancora lunga due unità, anche in UTF-32.
Adam D. Ruppe,

62

Dalla documentazione della String.Lengthproprietà:

La proprietà Lunghezza restituisce il numero di oggetti Char in questa istanza, non il numero di caratteri Unicode. La ragione è che un carattere Unicode potrebbe essere rappresentato da più di un Char . Utilizzare la classe System.Globalization.StringInfo per lavorare con ciascun carattere Unicode anziché con ogni carattere Char .


3
Java si comporta allo stesso modo (anche stampando 4 per String b), poiché utilizza la rappresentazione UTF-16 in array di caratteri. È un carattere a 4 byte in UTF-8.
Michael,

32

Il tuo personaggio all'indice 1 in "A𠈓C"è un SurrogatePair

Il punto chiave da ricordare è che le coppie surrogate rappresentano singoli caratteri a 32 bit .

Puoi provare questo codice e tornerà True

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

Metodo Char.IsSurrogatePair (String, Int32)

truese il parametro s include caratteri adiacenti all'indice di posizione e all'indice + 1 , e il valore numerico del carattere all'indice di posizione varia da U + D800 a U + DBFF e il valore numerico del carattere all'indice di posizione + 1 varia da U Da + DC00 a U + DFFF; in caso contrario, false.

Questo è ulteriormente spiegato nella proprietà String.Length :

La proprietà Lunghezza restituisce il numero di oggetti Char in questa istanza, non il numero di caratteri Unicode. Il motivo è che un personaggio Unicode potrebbe essere rappresentato da più di un carattere. Utilizzare la classe System.Globalization.StringInfo per lavorare con ciascun carattere Unicode anziché con ogni carattere Char.


24

Come hanno sottolineato le altre risposte, anche se ci sono 3 caratteri visibili sono rappresentati con 4 charoggetti. Questo è il motivo per cui Lengthè 4 e non 3.

MSDN afferma che

La proprietà Lunghezza restituisce il numero di oggetti Char in questa istanza, non il numero di caratteri Unicode.

Tuttavia, se ciò che vuoi veramente sapere è il numero di "elementi di testo" e non il numero di Charoggetti, puoi usare la StringInfoclasse.

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

Puoi anche enumerare ogni elemento di testo in questo modo

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

Usando foreachsulla stringa si dividerà la "lettera" centrale in due charoggetti e il risultato stampato non corrisponderà alla stringa.


20

Questo perché la Lengthproprietà restituisce il numero di oggetti char , non il numero di caratteri unicode. Nel tuo caso, uno dei caratteri Unicode è rappresentato da più di un oggetto char (SurrogatePair).

La proprietà Lunghezza restituisce il numero di oggetti Char in questa istanza, non il numero di caratteri Unicode. Il motivo è che un personaggio Unicode potrebbe essere rappresentato da più di un carattere. Utilizzare la classe System.Globalization.StringInfo per lavorare con ciascun carattere Unicode anziché con ogni carattere Char.


1
Hai un uso ambiguo di "carattere" in questa risposta. Suggerisco di sostituire almeno il primo con una terminologia precisa.
Corse di leggerezza in orbita,

1
Grazie. Risolto il problema con l'ambiguità.
Yuval Itzchakov,

10

Come altri hanno detto, non è il numero di caratteri nella stringa ma il numero di oggetti Char. Il carattere 𠈓 è il punto di codice U + 20213. Poiché il valore è al di fuori dell'intervallo del tipo di carattere a 16 bit, è codificato in UTF-16 come coppia surrogata D840 DE13.

Il modo per ottenere la lunghezza dei personaggi è stato menzionato nelle altre risposte. Tuttavia, dovrebbe essere usato con cura in quanto ci possono essere molti modi per rappresentare un personaggio in Unicode. "à" può essere 1 carattere composto o 2 caratteri (a + segni diacritici). La normalizzazione può essere necessaria come nel caso di Twitter .

Dovresti leggere questo
Il minimo assoluto che ogni sviluppatore di software deve assolutamente conoscere positivamente su Unicode e set di caratteri (senza scuse!)


6

Questo perché length()funziona solo per i punti di codice Unicode che non sono più grandi di U+FFFF. Questo set di punti di codice è noto come Basic Multilingual Plane (BMP) e utilizza solo 2 byte.

I punti di codice Unicode esterni a BMPsono rappresentati in UTF-16 usando coppie surrogate a 4 byte.

Per contare correttamente il numero di caratteri (3), utilizzare StringInfo

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

6

Ok, in .Net e C # tutte le stringhe sono codificate come UTF-16LE . A stringè memorizzato come una sequenza di caratteri. Ciascuno charincapsula la memoria di 2 byte o 16 bit.

Ciò che vediamo "su carta o schermo" come una singola lettera, carattere, glifo, simbolo o segno di punteggiatura può essere considerato come un singolo elemento di testo. Come descritto nella SEGMENTAZIONE DEL TESTO UNICODE standard Unicode n. 29 , ogni elemento di testo è rappresentato da uno o più punti di codice. Un elenco esaustivo di codici è disponibile qui .

Ogni punto di codice deve essere codificato in binario per la rappresentazione interna da un computer. Come detto, ognuno charmemorizza 2 byte. I punti di codice in o sotto U+FFFFpossono essere memorizzati in un singolo char. I punti di codice sopra U+FFFFsono memorizzati come coppia surrogata, usando due caratteri per rappresentare un singolo punto di codice.

Dato ciò che ora sappiamo di poter dedurre, un elemento di testo può essere memorizzato come uno char, come coppia surrogata di due caratteri o, se l'elemento di testo è rappresentato da più punti di codice una combinazione di singoli caratteri e coppie di surrogati. Come se ciò non fosse abbastanza complicato, alcuni elementi di testo possono essere rappresentati da diverse combinazioni di punti di codice come descritto in, Unicode Standard Annex # 15, UNICODE NORMALIZATION FORMS .


Interludio

Quindi, le stringhe che sembrano uguali quando vengono renderizzate possono effettivamente essere costituite da una diversa combinazione di caratteri. Un confronto ordinale (byte per byte) di due di queste stringhe rileverebbe una differenza, ciò potrebbe essere inatteso o indesiderabile.

È possibile ricodificare le stringhe .Net. in modo che utilizzino lo stesso modulo di normalizzazione. Una volta normalizzati, due stringhe con gli stessi elementi di testo verranno codificate allo stesso modo. Per fare ciò, utilizzare la funzione string.Normalize . Tuttavia, ricorda, alcuni elementi di testo diversi sono simili tra loro. :-S


Quindi, cosa significa tutto ciò in relazione alla domanda? L'elemento di testo '𠈓'è rappresentato dalla singola estensione del punto di codice U + 20213 cjk ideogrammi unificati b . Ciò significa che non può essere codificato come singolo chare deve essere codificato come coppia surrogata, utilizzando due caratteri. Questo è il motivo per cui string buno è charpiù lungo string a.

Se devi contare in modo affidabile (vedi avvertenza) il numero di elementi di testo in a string, dovresti usare la System.Globalization.StringInfoclasse in questo modo.

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

dando l'output,

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

come previsto.


Avvertimento

L'implementazione .Net della segmentazione del testo Unicode nelle classi StringInfoe TextElementEnumeratordovrebbe essere generalmente utile e, nella maggior parte dei casi, produrrà una risposta che il chiamante si aspetta. Tuttavia, come indicato nell'Allegato n. 29 dello standard Unicode, "L'obiettivo di abbinare le percezioni degli utenti non può sempre essere raggiunto esattamente perché il solo testo non contiene sempre informazioni sufficienti per decidere inequivocabilmente i limiti".


Penso che la tua risposta sia potenzialmente confusa. In questo caso, 𠈓 è solo un singolo punto di codice, ma poiché il suo punto di codice supera 0xFFFF, deve essere rappresentato come 2 unità di codice usando una coppia surrogata. Grapheme è un altro concetto costruito sopra il punto di codice, in cui un grapheme può essere rappresentato da un singolo punto di codice o più punti di codice, come si vede nell'Hangul coreano o in molte lingue latine.
nhahtdh,

@nhahtdh, sono d'accordo, la mia risposta è stata errata. L'ho riscritto e spero che ora crei maggiore chiarezza.
Jodrell,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.