Ten kod:
string a = "abc";
string b = "A𠈓C";
Console.WriteLine("Length a = {0}", a.Length);
Console.WriteLine("Length b = {0}", b.Length);
wyjścia:
Length a = 3
Length b = 4
Czemu? Jedyne, co mogłem sobie wyobrazić, to to, że chiński znak ma 2 bajty i że .Length
metoda zwraca liczbę bajtów.
𠈓
to 131603, a ponieważ znaki są bajtami bez znaku, oznacza to, że można uzyskać tę wartość w 2 znakach zamiast 4 (maksymalna wartość 16-bitowa bez znaku to 65535 (lub 65536 wariacji) i użycie 2 znaków do reprezentacji pozwala dla maksymalnej liczby zmian wynoszącej nie 65536 * 2 (131072), ale raczej 65536 * 65536 wariacji (4294967296, w rzeczywistości wartość 32-bitowa)Odpowiedzi:
Wszyscy inni dają powierzchowną odpowiedź, ale jest też głębsze uzasadnienie: liczba „znaków” jest trudnym do zdefiniowania pytaniem i może być zaskakująco kosztowna do obliczenia, podczas gdy właściwość length powinna być szybka.
Dlaczego trudno to zdefiniować? Cóż, jest kilka opcji i żadna nie jest tak naprawdę ważniejsza niż inna:
Liczba jednostek kodu (bajtów lub innych fragmentów danych o stałym rozmiarze; C # i Windows zwykle używają UTF-16, więc zwraca liczbę elementów dwubajtowych) jest z pewnością istotna, ponieważ komputer nadal musi radzić sobie z danymi w tej formie do wielu celów (np. zapis do pliku dba o bajty, a nie o znaki)
Liczba punktów kodowych Unicode jest dość łatwa do obliczenia (chociaż O (n), ponieważ musisz zeskanować ciąg w poszukiwaniu par zastępczych) i może mieć znaczenie dla edytora tekstu ... ale w rzeczywistości nie jest tym samym, co liczba znaków wydrukowane na ekranie (zwane grafemami). Na przykład, niektóre litery akcentowane mogą być reprezentowane w dwóch formach: jeden kod lub dwa punkty połączone razem, jeden reprezentujący literę i jeden mówiący „dodaj akcent do mojej litery partnera”. Czy para byłaby dwiema postaciami czy jedną? Aby w tym pomóc, możesz znormalizować ciągi znaków, ale nie wszystkie prawidłowe litery mają jedną reprezentację punktu kodowego.
Nawet liczba grafemów nie jest taka sama jak długość drukowanego ciągu, która zależy między innymi od czcionki, a ponieważ niektóre znaki są drukowane z pewnym nakładaniem się w wielu czcionkach (kerning), długość ciągu na ekranie i tak niekoniecznie jest równa sumie długości grafemów!
Niektóre punkty Unicode nie są nawet znakami w tradycyjnym sensie, ale raczej rodzajem znacznika kontrolnego. Podobnie jak znacznik kolejności bajtów lub wskaźnik od prawej do lewej. Czy to się liczy?
Krótko mówiąc, długość łańcucha jest w rzeczywistości absurdalnie złożonym pytaniem, a obliczenie jej może zająć dużo czasu procesora, a także tabele danych.
Co więcej, o co chodzi? Dlaczego te dane mają znaczenie? Cóż, tylko ty możesz odpowiedzieć na to pytanie w swoim przypadku, ale osobiście uważam, że są one generalnie nieistotne. Uważam, że ograniczanie wprowadzania danych jest bardziej logiczne dzięki ograniczeniom bajtów, ponieważ i tak trzeba je przesłać lub przechowywać. Ograniczanie rozmiaru wyświetlacza jest lepiej wykonywane przez oprogramowanie po stronie wyświetlacza - jeśli masz 100 pikseli dla wiadomości, to ile znaków zmieścisz zależy od czcionki itp., Które i tak nie są znane oprogramowaniu warstwy danych. Wreszcie, biorąc pod uwagę złożoność standardu Unicode, prawdopodobnie i tak będziesz mieć błędy w skrajnych przypadkach, jeśli spróbujesz czegoś innego.
Jest to więc trudne pytanie, przy niewielkim zastosowaniu ogólnym. Liczba jednostek kodu jest łatwa do obliczenia - jest to tylko długość podstawowej tablicy danych - i co do zasady jest najbardziej znacząca / użyteczna, z prostą definicją.
Dlatego
b
ma długość4
wykraczającą poza powierzchowne wyjaśnienie „ponieważ tak mówi dokumentacja”.źródło
Length
powinno być przestarzałe, aby zachować analogię z tablicami.Z dokumentacji o
String.Length
nieruchomości:źródło
String b
), ponieważ używa reprezentacji UTF-16 w tablicach char. Jest to 4-bajtowy znak w UTF-8.Twoja postać w indeksie 1 w
"A𠈓C"
to SurrogatePairMożesz wypróbować ten kod, a on zwróci
True
Char.IsSurrogatePair, metoda (String, Int32)
Jest to dokładniej wyjaśnione we właściwości String.Length :
źródło
Jak wskazywały inne odpowiedzi, nawet jeśli widoczne są 3 znaki, są one przedstawiane za pomocą 4
char
obiektów. Dlatego właśnieLength
jest 4, a nie 3.MSDN stwierdza, że
Jednakże, jeśli naprawdę chcesz wiedzieć, ile "elementów tekstowych", a nie liczba
Char
obiektów, możesz użyć tejStringInfo
klasy.Możesz także wyliczyć każdy element tekstowy w ten sposób
Użycie
foreach
na łańcuchu podzieli środkową „literę” na dwachar
obiekty, a wydrukowany wynik nie będzie odpowiadał napisowi.źródło
Dzieje się tak, ponieważ
Length
właściwość zwraca liczbę obiektów char , a nie liczbę znaków Unicode. W twoim przypadku jeden ze znaków Unicode jest reprezentowany przez więcej niż jeden obiekt char (SurrogatePair).źródło
Jak powiedzieli inni, nie jest to liczba znaków w ciągu, ale liczba obiektów Char. Znak 𠈓 to punkt kodowy U + 20213. Ponieważ wartość jest poza zakresem 16-bitowych znaków, jest zakodowana w UTF-16 jako para zastępcza
D840 DE13
.Sposób uzyskania długości znaków został wymieniony w innych odpowiedziach. Jednak należy go używać ostrożnie, ponieważ może istnieć wiele sposobów reprezentowania znaku w Unicode. „à” może składać się z 1 złożonego znaku lub 2 znaków (a + znaki diakrytyczne). Może być potrzebna normalizacja, tak jak w przypadku Twittera .
Powinieneś przeczytać to
Absolutne minimum Każdy programista Absolutnie, pozytywnie musi wiedzieć o Unicode i zestawach znaków (bez wymówek!)
źródło
Dzieje się tak, ponieważ
length()
działa tylko dla punktów kodowych Unicode, które nie są większe niżU+FFFF
. Ten zestaw punktów kodowych jest znany jako podstawowa płaszczyzna wielojęzyczna (BMP) i wykorzystuje tylko 2 bajty.Punkty kodu Unicode poza
BMP
znakami są reprezentowane w UTF-16 przy użyciu 4-bajtowych par zastępczych.Aby poprawnie policzyć liczbę znaków (3), użyj
StringInfo
źródło
Okay, w .Net i C # wszystkie ciągi są kodowane jako UTF-16LE . A
string
jest przechowywany jako sekwencja znaków. Każdychar
hermetyzuje pamięć 2 bajtów lub 16 bitów.To, co widzimy „na papierze lub ekranie” jako pojedyncza litera, znak, glif, symbol lub znak interpunkcyjny, można traktować jako pojedynczy element tekstowy. Jak opisano w załączniku nr 29 do standardu Unicode, SEGMENTACJA TEKSTU UNICODE , każdy element tekstowy jest reprezentowany przez jeden lub więcej punktów kodowych. Pełną listę kodów można znaleźć tutaj .
Każdy punkt kodowy musi zostać zakodowany w postaci binarnej w celu wewnętrznej reprezentacji przez komputer. Jak wspomniano, każdy
char
przechowuje 2 bajty. Punkty kodowe na lub poniżejU+FFFF
mogą być przechowywane w jednymchar
. Powyższe punkty kodoweU+FFFF
są przechowywane jako para zastępcza, przy użyciu dwóch znaków reprezentujących pojedynczy punkt kodowy.Biorąc pod uwagę to, co teraz wiemy, że możemy wydedukować, element tekstowy może być przechowywany jako jeden
char
, jako para zastępcza dwóch znaków lub, jeśli element tekstowy jest reprezentowany przez wiele punktów kodowych, pewna kombinacja pojedynczych znaków i par zastępczych. Jakby to nie było wystarczająco skomplikowane, niektóre elementy tekstowe mogą być reprezentowane przez różne kombinacje punktów kodowych, jak opisano w Załączniku nr 15 do normy Unicode, FORMY NORMALIZACJI UNICODE .Interludium
Tak więc ciągi, które wyglądają tak samo po wyrenderowaniu, mogą w rzeczywistości składać się z innej kombinacji znaków. Porządkowe (bajt po bajcie) porównanie dwóch takich ciągów wykryłoby różnicę, może to być nieoczekiwane lub niepożądane.
Możesz ponownie zakodować ciągi .Net. aby używali tego samego formularza normalizacji. Po znormalizowaniu dwa ciągi z tymi samymi elementami tekstowymi zostaną zakodowane w ten sam sposób. Aby to zrobić, użyj funkcji string.Normalize . Pamiętaj jednak, że niektóre różne elementy tekstowe wyglądają podobnie do siebie. : -s
Więc co to wszystko oznacza w odniesieniu do pytania? Element tekstowy
'𠈓'
jest reprezentowany przez pojedyncze rozszerzenie ujednoliconych ideogramów U + 20213 cjk b . Oznacza to, że nie może być zakodowany jako pojedynczychar
i musi być zakodowany jako para zastępcza, przy użyciu dwóch znaków. Dlategostring b
jest o jedenchar
dłużejstring a
.Jeśli potrzebujesz rzetelnie (patrz zastrzeżenie) policzyć liczbę elementów tekstowych w a
string
, powinieneś użyć takiejSystem.Globalization.StringInfo
klasy.dając wyjście,
zgodnie z oczekiwaniami.
Caveat
Implementacja .Net segmentacji tekstu Unicode w klasach
StringInfo
iTextElementEnumerator
powinna być ogólnie użyteczna i, w większości przypadków, przyniesie odpowiedź, której oczekuje wywołanie. Jednak, jak stwierdzono w Załączniku nr 29 standardu Unicode, „Cel dopasowania percepcji użytkownika nie zawsze może zostać dokładnie osiągnięty, ponieważ sam tekst nie zawsze zawiera wystarczającą ilość informacji, aby jednoznacznie określić granice”.źródło