Czy UTF-16 należy uważać za szkodliwy?

432

Zadam pytanie, które jest prawdopodobnie dość kontrowersyjnym pytaniem: „Czy jedno z najpopularniejszych kodowań, UTF-16, należy uznać za szkodliwe?”

Dlaczego zadaję to pytanie?

Ilu programistów jest świadomych faktu, że UTF-16 jest tak naprawdę kodowaniem o zmiennej długości? Rozumiem przez to, że istnieją punkty kodowe, reprezentowane jako pary zastępcze, biorą więcej niż jeden element.

Wiem; wiele aplikacji, struktur i interfejsów API korzysta z UTF-16, takich jak String Java, String C #, Win32 API, biblioteki Qt GUI, biblioteka Unicode ICU itp. Jednak przy tym wszystkim jest wiele podstawowych błędów w przetwarzaniu znaków poza BMP (znaki, które powinny być zakodowane przy użyciu dwóch elementów UTF-16).

Na przykład spróbuj edytować jeden z następujących znaków:

  • 𝄞 ( U + 1D11E ) SYMBOL MUZYCZNY G CLEF
  • 𝕥 ( U + 1D565 ) MATEMATYCZNY DOUBLE-STRUCK MAŁY T.
  • 𝟶 ( U + 1D7F6 ) MATEMATYCZNA CYFRA MONOSPACE ZERO
  • 𠂊 ( U + 2008A ) Han Character

Możesz przegapić niektóre, w zależności od zainstalowanych czcionek. Wszystkie te postacie znajdują się poza BMP (Basic Multilingual Plane). Jeśli nie widzisz tych znaków, możesz także spróbować spojrzeć na nie w opisie Znaków Unicode .

Na przykład spróbuj utworzyć nazwy plików w systemie Windows zawierające te znaki; spróbuj usunąć te znaki za pomocą „backspace”, aby zobaczyć, jak zachowują się w różnych aplikacjach korzystających z UTF-16. Zrobiłem kilka testów, a wyniki są dość złe:

  • Opera ma problem z ich edycją (usuń wymagane 2 naciśnięcia na backspace)
  • Notatnik nie radzi sobie z nimi poprawnie (usuń wymagane 2 naciśnięcia na backspace)
  • Edycja nazw plików w oknach dialogowych okna w podziale (usuń wymagane 2 naciśnięcia na backspace)
  • Wszystkie aplikacje QT3 nie radzą sobie z nimi - pokazują dwa puste kwadraty zamiast jednego symbolu.
  • Python koduje takie znaki niepoprawnie, gdy są używane bezpośrednio u'X'!=unicode('X','utf-16')na niektórych platformach, gdy znak X poza BMP.
  • Unicodedata w Pythonie 2.5 nie uzyskuje właściwości takich znaków, gdy Python skompilowany z ciągami znaków Unicode UTF-16.
  • StackOverflow wydaje się usuwać te znaki z tekstu, jeśli są edytowane bezpośrednio jako znaki Unicode (znaki te są wyświetlane za pomocą znaków ucieczki Unicode HTML).
  • TextFox WinForm może generować niepoprawny ciąg znaków, gdy jest ograniczony przez MaxLength.

Wydaje się, że takie błędy są niezwykle łatwe do znalezienia w wielu aplikacjach korzystających z UTF-16.

Więc ... Czy uważasz, że UTF-16 należy uznać za szkodliwy?

Artyom
źródło
64
Niezupełnie poprawne. Wyjaśniam, że jeśli napiszesz „שָׁ” znak złożony, który składa się z „ש”, „ָ” i „ׁ”, vovels, to usunięcie każdego z nich jest logiczne, usuwasz jeden kod po naciśnięciu „ backspace ”i usuń wszystkie znaki, w tym także vovels, po wciśnięciu„ del ”. Ale nigdy nie produkujesz nielegalnego stanu tekstu - nielegalnych punktów kodowych. Tak więc sytuacja, gdy naciśniesz klawisz Backspace i uzyskasz nielegalny tekst, jest niepoprawna.
41
CiscoIPPhone: Jeśli błąd jest „zgłaszany kilka razy przez wiele różnych osób”, a następnie kilka lat później programista pisze na blogu deweloperów, że „Wierzcie lub nie, zachowanie jest głównie zamierzone!”, A następnie to delikatnie). Myślę, że prawdopodobnie nie jest to najlepsza decyzja projektowa, jaką kiedykolwiek podjęto. :-) To, że jest celowe, nie oznacza, że ​​nie jest to błąd.
145
Wspaniały post. UTF-16 jest rzeczywiście „najgorszym z obu światów”: UTF8 ma zmienną długość, obejmuje cały Unicode, wymaga algorytmu transformacji do i od surowych punktów kodowych, ogranicza się do ASCII i nie ma problemów z endianią. UTF32 ma stałą długość, nie wymaga transformacji, ale zajmuje więcej miejsca i ma problemy z endianizmem. Do tej pory można używać UTF32 wewnętrznie i UTF8 do serializacji. Ale UTF16 nie ma żadnych zalet: jest zależny od endianów, ma zmienną długość, zajmuje dużo miejsca, nie jest kompatybilny z ASCII. Wysiłek potrzebny do właściwego radzenia sobie z UTF16 można lepiej wydać na UTF8.
Kerrek SB
26
@Ian: UTF-8 NIE MA takich samych zastrzeżeń jak UTF-8. Nie możesz mieć surogatów w UTF-8. UTF-8 nie podszywa się pod coś, czym nie jest, ale większość programistów używających UTF-16 niewłaściwie go używa. Wiem. Oglądałem je raz za razem, raz za razem, raz za razem.
tchrist
18
Ponadto UTF-8 nie ma problemu, ponieważ każdy traktuje go jako kodowanie o zmiennej szerokości. Przyczyną tego problemu jest UTF-16, ponieważ wszyscy traktują go jak kodowanie o stałej szerokości.
Christoffer Hammarström

Odpowiedzi:

340

To stara odpowiedź. Najnowsze aktualizacje można
znaleźć w UTF-8 Everywhere .

Opinia: Tak, UTF-16 należy uznać za szkodliwy . Powodem tego jest fakt, że jakiś czas temu istniało błędne przekonanie, że widechar będzie tym, czym jest teraz UCS-4.

Pomimo „anglo-centralizmu” UTF-8 należy go uznać za jedyne przydatne kodowanie tekstu. Można argumentować, że kody źródłowe programów, stron internetowych i plików XML, nazwy plików systemu operacyjnego i inne interfejsy tekstowe między komputerami nigdy nie powinny istnieć. Ale kiedy to robią, tekst jest nie tylko dla ludzkich czytelników.

Z drugiej strony koszty ogólne UTF-8 to niewielka cena do zapłacenia, która ma znaczące zalety. Zalety, takie jak zgodność z nieświadomym kodem, który po prostu przekazuje ciągi znaków char*. To jest świetna rzecz. Istnieje kilka użytecznych postaci, które są SHORTER w UTF-16 niż w UTF-8.

Wierzę, że wszystkie inne kodowania w końcu umrą. Wiąże się to z tym, że MS-Windows, Java, ICU, python przestają używać go jako swojego ulubionego. Po długich badaniach i dyskusjach konwencje programistyczne w mojej firmie zabraniają używania UTF-16 w dowolnym miejscu z wyjątkiem wywołań API OS, a to pomimo znaczenia wydajności w naszych aplikacjach i faktu, że używamy Windows. Funkcje konwersji zostały opracowane w celu konwersji zawsze zakładanego UTF8 std::stringna natywny UTF-16, który sam system Windows nie obsługuje poprawnie .

Ludziom, którzy mówią „ używaj tego, co potrzebne, tam, gdzie jest to potrzebne ”, mówię: ogromną zaletą jest stosowanie wszędzie tego samego kodowania i nie widzę wystarczającego powodu, by robić inaczej. W szczególności myślę, że dodanie wchar_tdo C ++ było błędem, podobnie jak dodatki Unicode do C ++ 0x. Jednak od implementacji STL należy wymagać, aby każdy parametr std::stringlub char*parametr był uważany za zgodny z Unicode.

Jestem także przeciwny podejściu „ używaj tego, co chcesz ”. Nie widzę powodu do takiej wolności. Występuje dość zamieszania na temat tekstu, co powoduje, że całe to zepsute oprogramowanie. Powiedziawszy powyżej, jestem przekonany, że programiści muszą wreszcie osiągnąć konsensus w sprawie UTF-8 jako jeden właściwy sposób. (Pochodzę z kraju, który nie mówi w ascii i dorastałem w systemie Windows, więc po raz ostatni oczekuje się, że zaatakuję UTF-16 z powodów religijnych).

Chciałbym udostępnić więcej informacji o tym, jak piszę tekst w systemie Windows i co polecam wszystkim innym, aby sprawdzić poprawność Unicode podczas kompilacji, łatwość użycia i lepszą wieloplatformowość kodu. Sugestia zasadniczo różni się od tego, co jest zwykle zalecane jako właściwy sposób używania Unicode w systemie Windows. Jednak dogłębne badanie tych zaleceń doprowadziło do tego samego wniosku. Więc oto idzie:

  • Nie należy używać wchar_tani std::wstringw żadnym innym miejscu niż przylegający punkt do interfejsów API akceptujących UTF-16.
  • Nie wolno używać _T("")lub L""UTF-16 literały (IMO te powinny być wyjęte z normą, jako część UTF-16 amortyzację).
  • Nie używaj typów, funkcji lub ich pochodnych wrażliwych na _UNICODEstałą, takich jak LPTSTRlub CreateWindow().
  • Jednak _UNICODEzawsze zdefiniowane, aby uniknąć przekazywania char*ciągów do WinAPI podczas cichej kompilacji
  • std::stringsi char*gdziekolwiek w programie są uważane za UTF-8 (jeśli nie podano inaczej)
  • Wszystkie moje ciągi są std::string, chociaż możesz przekazać char * lub literał ciąg do convert(const std::string &).
  • używaj tylko funkcji Win32, które akceptują widechars ( LPWSTR). Nigdy nie akceptują LPTSTRlub LPSTR. Przekaż parametry w ten sposób:

    ::SetWindowTextW(Utils::convert(someStdString or "string litteral").c_str())
    

    (Zasady używają funkcji konwersji poniżej.)

  • Z ciągami MFC:

    CString someoneElse; // something that arrived from MFC. Converted as soon as possible, before passing any further away from the API call:
    
    std::string s = str(boost::format("Hello %s\n") % Convert(someoneElse));
    AfxMessageBox(MfcUtils::Convert(s), _T("Error"), MB_OK);
    
  • Praca z plikami, nazwami plików i fstream w systemie Windows:

    • Nigdy nie przesyłaj argumentów std::stringlub const char*nazw plików do fstreamrodziny. MSVC STL nie obsługuje argumentów UTF-8, ale ma niestandardowe rozszerzenie, którego należy użyć w następujący sposób:
    • Konwersja std::stringargumentów std::wstringz Utils::Convert:

      std::ifstream ifs(Utils::Convert("hello"),
                        std::ios_base::in |
                        std::ios_base::binary);
      

      Będziemy musieli ręcznie usunąć konwersję, gdy stosunek MSVC do fstreamzmian.

    • Ten kod nie jest wieloplatformowy i może wymagać ręcznej zmiany w przyszłości
    • Więcej informacji można znaleźć w fstreamprzypadku 4215 dotyczącym badania / dyskusji w trybie Unicode.
    • Nigdy nie twórz wyjściowych plików tekstowych z zawartością inną niż UTF8
    • Unikaj używania fopen()z powodów RAII / OOD. W razie potrzeby użyj _wfopen()powyższych konwencji WinAPI.

// For interface to win32 API functions
std::string convert(const std::wstring& str, unsigned int codePage /*= CP_UTF8*/)
{
    // Ask me for implementation..
    ...
}

std::wstring convert(const std::string& str, unsigned int codePage /*= CP_UTF8*/)
{
    // Ask me for implementation..
    ...
}

// Interface to MFC
std::string convert(const CString &mfcString)
{
#ifdef UNICODE
    return Utils::convert(std::wstring(mfcString.GetString()));
#else
    return mfcString.GetString();   // This branch is deprecated.
#endif
}

CString convert(const std::string &s)
{
#ifdef UNICODE
    return CString(Utils::convert(s).c_str());
#else
    Exceptions::Assert(false, "Unicode policy violation. See W569"); // This branch is deprecated as it does not support unicode
    return s.c_str();   
#endif
}
Pavel Radzivilovsky
źródło
39
Nie mogę się zgodzić Przewaga utf16 nad utf8 dla wielu języków azjatyckich całkowicie dominuje nad twoimi punktami. Naiwnością jest mieć nadzieję, że Japończycy, Tajowie, Chińczycy itd. Zrezygnują z tego kodowania. Problematyczne starcia między zestawami znaków występują wtedy, gdy zestawy znaków wydają się w większości podobne, z wyjątkiem różnic. Sugeruję standaryzację na: naprawiono 7bit: iso-irv-170; 8-bitowa zmienna: utf8; 16-bitowa zmienna: utf16; Naprawiono 32 bity: ucs4.
82
@Charles: dziękuję za Twój wkład. To prawda, że ​​niektóre znaki BMP są dłuższe w UTF-8 niż w UTF-16. Ale spójrzmy prawdzie w oczy: problemem nie są bajty, które zabierają chińskie znaki BMP, ale złożoność projektu oprogramowania, która się pojawia. Jeśli chiński programista i tak musi zaprojektować znaki o zmiennej długości, wydaje się, że UTF-8 to wciąż niewielka cena do zapłacenia w porównaniu do innych zmiennych w systemie. Mógłby użyć UTF-16 jako algorytmu kompresji, jeśli przestrzeń jest tak ważna, ale nawet wtedy nie będzie pasować do LZ, a po LZ lub innej ogólnej kompresji oba będą miały ten sam rozmiar i entropię.
32
Mówię w zasadzie, że uproszczenie oferowane przez kodowanie One, które jest również kompatybilne z istniejącymi programami char *, a także jest obecnie najbardziej popularne, ponieważ wszystko jest niewyobrażalne. To prawie jak w starych, dobrych „tekstach jawnych”. Chcesz otworzyć plik o nazwie? Nie musisz się martwić, jaki rodzaj Unicode robisz itp. Sugerujemy, abyśmy, programiści, ograniczyli UTF-16 do bardzo szczególnych przypadków poważnej optymalizacji, w których niewielka wydajność jest warta wiele miesięcy pracy.
17
Przy wyborze wewnętrznego UTF-8 Linux miał określone wymagania: kompatybilność z Uniksem. Windows tego nie potrzebował, dlatego też, gdy programiści wdrożyli Unicode, dodali wersje prawie wszystkich funkcji obsługujących tekst w UCS-2 i sprawili, że te wielobajtowe po prostu przekonwertowały się na UCS-2 i wywołały pozostałe. Później zastępuje UCS-2 UTF-16. Linux z drugiej strony przechowywał kodowanie 8-bitowe i dlatego używał UTF-8, ponieważ w tym przypadku jest to właściwy wybór.
Mircea Chirea
34
@Pavel Radzivilovsky: BTW, twoje pisma na temat „Wierzę, że wszystkie inne kodowania w końcu umrą. Oznacza to, że MS-Windows, Java, ICU, python przestają używać go jako swojego ulubionego”. i „W szczególności myślę, że dodanie wchar_t do C ++ było błędem, podobnie jak dodatki Unicode do C ++ Ox”. są albo dość naiwni, albo bardzo bardzo aroganccy. A to pochodzi od kogoś, kto koduje w domu za pomocą Linuksa i który jest zadowolony z znaków UTF-8. Mówiąc wprost: to się nie stanie .
paercebal,
157

Punkty kodowe Unicode nie są znakami! Czasami nie są nawet glifami (formami wizualnymi).

Kilka przykładów:

  • Liczby rzymskie takie jak „ⅲ”. (Pojedynczy znak, który wygląda jak „iii”.)
  • Znaki akcentowane, takie jak „á”, które mogą być reprezentowane jako pojedynczy połączony znak „\ u00e1” lub znak i oddzielone znaki diakrytyczne „\ u0061 \ u0301”.
  • Znaki takie jak grecka sigma z małymi literami, które mają różne formy dla środka („σ”) i końca („ς”) pozycji słów, ale które należy traktować jako synonimy wyszukiwania.
  • Dowolny dyskretny łącznik U + 00AD, który może, ale nie musi być wyświetlany wizualnie, w zależności od kontekstu, i który jest ignorowany dla wyszukiwania semantycznego.

Jedynym sposobem na poprawną edycję Unicode jest użycie biblioteki napisanej przez eksperta lub zostać ekspertem i napisać własną. Jeśli tylko liczysz współrzędne, żyjesz w stanie grzechu.

Daniel Newby
źródło
19
To. Bardzo to. UTF-16 może powodować problemy, ale nawet używanie UTF-32 przez cały czas może (i będzie) powodować problemy.
bcat
11
Co to za postać? Możesz zdefiniować punkt kodowy jako znak i poradzić sobie całkiem dobrze. Jeśli masz na myśli glif widoczny dla użytkownika, to coś innego.
tchrist
7
@ tchrist na pewno do przydzielenia miejsca ta definicja jest w porządku, ale na cokolwiek innego? Nie tak bardzo. Jeśli traktujesz łączący znak jako jedyny znak (tj. W przypadku operacji usuwania lub „weź pierwsze N ​​znaków”), otrzymasz dziwne i złe zachowanie. Jeśli punkt kodowy ma znaczenie tylko w połączeniu z co najmniej innym, nie możesz sobie z nim poradzić w żaden rozsądny sposób.
Voo,
6
@Pacerier, jest już późno na imprezę, ale muszę to skomentować. Niektóre języki mają bardzo duże zestawy potencjalnych kombinacji znaków diakrytycznych (por. Wietnamski, tj. Mệt đừ). Bardzo pomocne są kombinacje zamiast jednego znaku na znak diakrytyczny.
asthasr
21
mała uwaga na terminologii: codepoints nie odpowiadają znaki Unicode ; Daniel mówi tutaj o postaciach postrzeganych przez użytkowników , które odpowiadają klastrom grafem unicode
Christoph
54

Istnieje prosta ogólna zasada dotycząca tego, jakiego formularza Unicode Transformation Form (UTF) użyć: - utf-8 do przechowywania i komunikacji - utf-16 do przetwarzania danych - możesz użyć utf-32, jeśli większość używanego interfejsu API platformy to utf-32 (powszechny w świecie UNIX).

Obecnie większość systemów używa utf-16 (Windows, Mac OS, Java, .NET, ICU, Qt). Zobacz także ten dokument: http://unicode.org/notes/tn12/

Wracając do „UTF-16 jako szkodliwego”, powiedziałbym: zdecydowanie nie.

Ludzie, którzy boją się surogatów (myśląc, że przekształcają Unicode w kodowanie o zmiennej długości) nie rozumieją innych (znacznie większych) złożoności, które sprawiają, że mapowanie między znakami i punktem kodu Unicode jest bardzo złożone: łączenie znaków, ligatur, selektorów wariacji , znaki kontrolne itp.

Przeczytaj tę serię tutaj http://www.siao2.com/2009/06/29/9800913.aspx i zobacz, jak UTF-16 staje się łatwym problemem.

Mihai Nita
źródło
26
Dodaj kilka przykładów, w których UTF-32 jest powszechny w świecie UNIX!
maxschlepzig
48
Nie, nie chcesz używać UTF-16 do przetwarzania danych. To boli w tyłek. Ma wszystkie wady UTF-8, ale żadna z jego zalet. Zarówno UTF-8, jak i UTF-32 są wyraźnie lepsze od złośliwego hacka znanego wcześniej jako Pani UTF-16, którego panieńskie nazwisko to UCS-2.
tchrist
34
Wczoraj właśnie znalazłem błąd w equalsIgnoreCasemetodzie klasy String w Javie (także inne w klasie string), który nigdy by nie istniał, gdyby Java użyła UTF-8 lub UTF-32. W każdym kodzie używającym UTF-16 są miliony tych śpiących bomb. Mam ich dość. UTF-16 to złośliwa ospa, która na zawsze plaga nasze oprogramowanie podstępnymi błędami. Jest to oczywiście szkodliwe i powinno być przestarzałe i zakazane.
tchrist
7
@ tchrist Wow, więc funkcja nie będąca surogatką (ponieważ została napisana, gdy jej nie było i jest niestety udokumentowana w taki sposób, że prawdopodobnie nie jest możliwa adaptacja - określa .toUpperCase (char)) spowoduje nieprawidłowe zachowanie? Wiesz, że funkcja UTF-32 z nieaktualną mapą punktów kodowych nie poradziłaby sobie z tym lepiej? Również całe API Java radzi sobie z surogatami niezbyt dobrze, a bardziej skomplikowane kwestie dotyczące Unicode wcale nie są - a później używane kodowanie nie miałoby żadnego znaczenia.
Voo,
8
-1: Bezwarunkowy .Substring(1)w .NET to trywialny przykład czegoś, co psuje obsługę wszystkich Unicode innych niż BMP. Wszystko, co korzysta z UTF-16, ma ten problem; zbyt łatwo jest traktować to jako kodowanie o stałej szerokości, a problemy występują zbyt rzadko. To sprawia, że ​​kodowanie jest aktywnie szkodliwe, jeśli chcesz obsługiwać Unicode.
Roman Starkov,
43

Tak, absolutnie.

Dlaczego? Ma to związek z ćwiczeniem kodu .

Jeśli spojrzysz na te statystyki wykorzystania współrzędnych kodowych na dużym korpusie autorstwa Toma Christiansena, zobaczysz, że trans-8-bitowe współrzędne BMP są używane o kilka rzędów, jeśli wielkość jest większa niż współrzędne non-BMP:

 2663710 U+002013 ‹–›  GC=Pd    EN DASH
 1065594 U+0000A0 ‹ ›  GC=Zs    NO-BREAK SPACE
 1009762 U+0000B1 ‹±›  GC=Sm    PLUS-MINUS SIGN
  784139 U+002212 ‹−›  GC=Sm    MINUS SIGN
  602377 U+002003 ‹ ›  GC=Zs    EM SPACE

 544 U+01D49E ‹𝒞›  GC=Lu    MATHEMATICAL SCRIPT CAPITAL C
 450 U+01D4AF ‹𝒯›  GC=Lu    MATHEMATICAL SCRIPT CAPITAL T
 385 U+01D4AE ‹𝒮›  GC=Lu    MATHEMATICAL SCRIPT CAPITAL S
 292 U+01D49F ‹𝒟›  GC=Lu    MATHEMATICAL SCRIPT CAPITAL D
 285 U+01D4B3 ‹𝒳›  GC=Lu    MATHEMATICAL SCRIPT CAPITAL X

Weź powiedzenie TDD: „Niesprawdzony kod to uszkodzony kod” i sformatuj go jako „niewyćwiczony kod to uszkodzony kod” i zastanów się, jak często programiści mają do czynienia z punktami kodowymi innymi niż BMP.

Błędy związane z nierozpoznawaniem UTF-16 jako kodowania o zmiennej szerokości są znacznie bardziej niezauważalne niż równoważne błędy w UTF-8 . Niektóre języki programowania wciąż nie gwarantują UTF-16 zamiast UCS-2, a niektóre tak zwane języki programowania wysokiego poziomu oferują dostęp do jednostek kodowych zamiast punktów kodowych (nawet C ma zapewniać dostęp do punkty kodowe, jeśli używasz wchar_t, niezależnie od tego, co mogą robić niektóre platformy).

ninjalj
źródło
16
„Błędy związane z niedziałaniem UTF-16 jako kodowania o zmiennej szerokości są znacznie bardziej niezauważalne niż błędy równoważne w UTF-8”. To jest sedno problemu, a zatem poprawna odpowiedź.
Sean McMillan,
3
Dokładnie. Jeśli twoje UTF-8 zostanie zniszczone, stanie się to natychmiast oczywiste. Jeśli obsługa UTF-8 jest zakłócona, zauważysz tylko, jeśli wstawisz nietypowe znaki Hana lub symbole matematyczne.
Ślimak mechaniczny
1
Bardzo prawda, ale z drugiej strony, po co są testy jednostkowe, jeśli powinieneś znaleźć szczęście w znalezieniu błędów w rzadszych przypadkach?
musiphil
@musiphil: więc kiedy ostatnio stworzyłeś test jednostkowy dla znaków spoza BMP?
ninjalj
1
Aby rozwinąć moje wcześniejsze oświadczenie: nawet w przypadku UTF-8 nie można mieć pewności, że uwzględniono wszystkie sprawy po obejrzeniu tylko kilku przykładów roboczych. To samo z UTF-16: musisz sprawdzić, czy twój kod działa zarówno z surogatami, jak i surogatami. (Ktoś mógłby nawet argumentować, że UTF-8 ma co najmniej cztery poważne przypadki, podczas gdy UTF-16 ma tylko dwa.)
Musiphil
40

Sugerowałbym, że myślenie, że UTF-16 może być uważany za szkodliwy, mówi, że musisz lepiej zrozumieć Unicode .

Poniewaz zostalam zlekcewazona za przedstawienie mojej opinii na subiektywne pytanie, pozwólcie, ze rozwinie sie. Co dokładnie przeszkadza ci w UTF-16? Wolałbyś, żeby wszystko było zakodowane w UTF-8? UTF-7? A może UCS-4? Oczywiście niektóre aplikacje nie są zaprojektowane do obsługi każdego pojedynczego kodu znaków, ale są niezbędne, szczególnie w dzisiejszej globalnej domenie informacyjnej, do komunikacji między granicami międzynarodowymi.

Ale tak naprawdę, jeśli uważasz, że UTF-16 powinien być uważany za szkodliwy, ponieważ jest mylący lub może być nieprawidłowo zaimplementowany (z pewnością może być Unicode), to jaką metodę kodowania znaków można uznać za nieszkodliwą?

EDYCJA: Wyjaśnienie: Dlaczego niewłaściwe implementacje normy odzwierciedlają jakość samej normy? Jak później zauważyli inni, fakt, że aplikacja niewłaściwie używa narzędzia, nie oznacza, że ​​samo narzędzie jest wadliwe. Gdyby tak było, moglibyśmy prawdopodobnie powiedzieć takie słowa, jak „słowo kluczowe var uważane za szkodliwe” lub „wątki uważane za szkodliwe”. Myślę, że pytanie to myli jakość i naturę standardu z trudnościami, jakie wielu programistów ma we wdrażaniu i stosowaniu go prawidłowo, co wydaje mi się bardziej związane z ich niezrozumieniem, jak działa Unicode, niż z samym Unicode.

patjbs
źródło
33
-1: A może zająć się niektórymi zarzutami Artema, a nie patronować mu?
8
BTW: Kiedy zacząłem pisać ten artykuł, prawie chciałem napisać „Czy Joel w Softeare artykuł Unicode powinien być uważany za szkodliwy”, ponieważ jest wiele błędów. Na przykład: kodowanie utf-8 zajmuje do 4 znaków, a nie 6. Ponadto nie rozróżnia UCS-2 i UTF-16, które są naprawdę różne - i faktycznie powodują problemy, o których mówię.
32
Należy również zauważyć, że kiedy Joel napisał ten artykuł, standard UTF-8 WAS 6 bajtów, a nie 4. RFC 3629 zmienił standard na 4 bajty kilka miesięcy PO napisaniu tego artykułu. Jak większość czegokolwiek w Internecie, warto czytać z więcej niż jednego źródła i być świadomym wieku swoich źródeł. Link nie miał być „końcem wszystkim być wszystkim”, ale raczej punktem wyjścia.
7
Zrobiłbym zdjęcie: utf-8 lub utf-32, które są: kodowaniem o zmiennej długości w prawie wszystkich przypadkach (w tym BMP) lub kodowaniem o stałej długości zawsze.
18
@iconiK: Nie bądź głupi. UTF-16 absolutnie nie jest de facto standardem przetwarzania tekstu. Pokaż mi język programowania, który jest bardziej odpowiedni do przetwarzania tekstu, niż Perl, który zawsze (cóż, przez ponad dekadę) używał abstrakcyjnych znaków z wewnętrzną reprezentacją UTF-8. Z tego powodu każdy program Perla automatycznie obsługuje cały Unicode bez konieczności ciągłego przeszukiwania idiotycznych surogatów. Długość łańcucha to jego liczba w punktach kodowych, a nie w jednostkach kodowych. Wszystko inne to zwykła głupota polegająca na wprowadzeniu wstecznej kompatybilności wstecznej.
tchrist
37

Nie ma nic złego w kodowaniu Utf-16. Ale języki, które traktują jednostki 16-bitowe jako znaki, należy prawdopodobnie uznać za źle zaprojektowane. Posiadanie typu o nazwie „ char”, który nie zawsze reprezentuje postać, jest dość mylące. Ponieważ większość programistów spodziewa się, że typ znaku reprezentuje punkt kodowy lub znak, znaczna część kodu prawdopodobnie się zepsuje, gdy zostanie wystawiona na znaki poza BMP.

Zauważ jednak, że nawet użycie utf-32 nie oznacza, że ​​każdy 32-bitowy punkt kodowy zawsze będzie reprezentował znak. Ze względu na łączenie znaków rzeczywisty znak może składać się z kilku punktów kodowych. Unicode nigdy nie jest trywialny.

BTW. Prawdopodobnie istnieje ta sama klasa błędów w platformach i aplikacjach, które oczekują 8-bitowych znaków, które są zasilane Utf-8.

JacquesB
źródło
12
W przypadku Javy, jeśli spojrzysz na ich oś czasu ( java.com/en/javahistory/timeline.jsp ), zobaczysz, że pierwotny rozwój String nastąpił, gdy Unicode miał 16 bitów (zmienił się w 1996 roku). Musieli oprzeć się na zdolności do obsługi punktów niezgodnych z kodem BMP, co spowodowało zamieszanie.
Kathy Van Stone
10
@Kathy: Nie jest to jednak wymówka dla C #. Generalnie zgadzam się, że powinien istnieć CodePointtyp, zawierający pojedynczy punkt kodowy (21 bitów), CodeUnittyp, zawierający pojedynczą jednostkę kodową (16 bitów dla UTF-16), a Charactertyp idealnie musiałby obsługiwać kompletny grafem. Ale to sprawia, że ​​jest to funkcjonalnie równoważne z String...
Joey,
1
Ta odpowiedź ma prawie dwa lata, ale nie mogę nie skomentować. „Posiadanie typu o nazwie„ char ”, który nie zawsze reprezentuje postać, jest dość mylące.” A jednak ludzie używają go cały czas w C i tym podobnych do reprezentowania danych liczb całkowitych, które mogą być przechowywane w jednym bajcie.
JAB
I widziałem dużo kodu C, który nie obsługuje poprawnie kodowania znaków.
dan04,
1
C # ma inną wymówkę: został zaprojektowany dla systemu Windows, a system Windows został zbudowany na UCS-2 (to bardzo denerwujące, że nawet dziś interfejsy API systemu Windows nie obsługują UTF-8). Plus, myślę, że Microsoft chciał kompatybilności z Javą (.NET 1.0 miał bibliotekę kompatybilności z Javą, ale bardzo szybko porzucił obsługę Java - Zgaduję, że jest to spowodowane pozwem Sun przeciwko MS?)
Qwertie
20

Moim osobistym wyborem jest zawsze używanie UTF-8. Jest to standard w Linuksie dla prawie wszystkiego. Jest wstecznie kompatybilny z wieloma starszymi aplikacjami. Jest bardzo minimalny narzut pod względem dodatkowej przestrzeni używanej dla znaków niełacińskich w porównaniu z innymi formatami UTF, oraz znaczna oszczędność miejsca dla znaków łacińskich. W Internecie królują języki łacińskie i myślę, że tak będzie w najbliższej przyszłości. I aby odnieść się do jednego z głównych argumentów w oryginalnym poście: prawie każdy programista jest świadomy, że UTF-8 czasami zawiera znaki wielobajtowe. Nie wszyscy radzą sobie z tym poprawnie, ale zwykle są świadomi, co jest więcej niż można powiedzieć o UTF-16. Ale oczywiście musisz wybrać najbardziej odpowiedni dla swojej aplikacji. Właśnie dlatego jest ich więcej niż jeden.

rmeador
źródło
3
UTF-16 jest prostszy do wszystkiego w BMP, dlatego jest tak szeroko stosowany. Ale jestem też fanem UTF-8, nie ma też problemów z kolejnością bajtów, co działa na jego korzyść.
Malcolm
2
Teoretycznie tak. W praktyce istnieją takie rzeczy jak, powiedzmy, UTF-16BE, co oznacza UTF-16 w big endian bez BOM. Nie jest to coś, co wymyśliłem, jest to faktyczne kodowanie dozwolone w tagach ID3v2.4 (tagi ID3v2 są do bani, ale są niestety szeroko stosowane). I w takich przypadkach musisz zdefiniować endianness zewnętrznie, ponieważ sam tekst nie zawiera BOM. UTF-8 jest zawsze napisany w jedną stronę i nie ma takiego problemu.
Malcolm
23
Nie, UTF-16 nie jest prostszy. To jest trudniejsze. Wprowadza w błąd i zwodzi cię do myślenia, że ​​ma stałą szerokość. Cały taki kod jest zepsuty i cała obyczajowość, ponieważ nie zauważysz, dopóki nie będzie za późno. PRZYPADEK: Wczoraj znalazłem kolejny głupi błąd UTF-16 w bibliotekach rdzenia Java, tym razem w String.equalsIgnoreCase, który został pozostawiony w buggery braindeath UCS-2, a więc zawodzi w przypadku poprawnych punktów kodu Unicode 16/17. Jak długo istnieje ten kod? Nie ma usprawiedliwienia dla buggy. UTF-16 prowadzi do czystej głupoty i wypadku, który czeka. Uruchom krzyczenie z UTF-16.
tchrist
3
@ tchrist Trzeba być bardzo ignorantem, aby nie wiedzieć, że UTF-16 nie ma ustalonej długości. Jeśli zaczniesz od Wikipedii, na samym górze przeczytasz: „Daje wynik o zmiennej długości jednej lub dwóch 16-bitowych jednostek kodu na punkt kodowy”. FAQ o Unicode mówi to samo: unicode.org/faq//utf_bom.html#utf16-1 . Nie wiem, w jaki sposób UTF-16 może zwieść kogokolwiek, jeśli jest wszędzie napisane, że ma zmienną długość. Jeśli chodzi o metodę, nigdy nie została zaprojektowana dla UTF-16 i nie powinna być traktowana jako Unicode, tak prosta.
Malcolm,
2
@tchrist Czy masz źródło swoich statystyk? Chociaż dobrzy programiści są rzadkością, myślę, że to dobrze, ponieważ stajemy się bardziej wartościowi. :) Jeśli chodzi o interfejsy API Java, części oparte na znakach mogą w końcu zostać uznane za przestarzałe, ale nie stanowi to gwarancji, że nie będą używane. I na pewno nie zostaną usunięte ze względu na kompatybilność.
Malcolm,
18

Istnieje kodowanie wykorzystujące symbole o stałym rozmiarze. Z pewnością mam na myśli UTF-32. Ale 4 bajty na każdy symbol to za dużo zmarnowanej przestrzeni, dlaczego mielibyśmy go używać w codziennych sytuacjach?

Moim zdaniem większość problemów wynika z faktu, że niektóre programy nie nadążały za standardem Unicode, ale nie szybko zaradziły tej sytuacji. Opera, Windows, Python, Qt - wszystkie pojawiły się, zanim UTF-16 stał się powszechnie znany, a nawet powstał. Mogę jednak potwierdzić, że w Operze, Eksploratorze Windows i Notatniku nie ma już problemów ze znakami spoza BMP (przynajmniej na moim komputerze). Ale w każdym razie, jeśli programy nie rozpoznają par zastępczych, to nie używają UTF-16. Jakiekolwiek problemy wynikają z radzenia sobie z takimi programami, nie mają one nic wspólnego z samym UTF-16.

Myślę jednak, że problemy ze starszym oprogramowaniem z obsługą tylko BMP są nieco przesadzone. Znaki spoza BMP występują tylko w bardzo szczególnych przypadkach i obszarach. Według oficjalnego FAQ Unicode „nawet w tekście wschodnioazjatyckim częstość występowania par zastępczych powinna wynosić średnio znacznie mniej niż 1% całej pamięci tekstowej”. Oczywiście, znaki spoza BMP nie powinny być pomijane, ponieważ w przeciwnym razie program nie jest zgodny z Unicode, ale większość programów nie jest przeznaczona do pracy z tekstami zawierającymi takie znaki. Dlatego jeśli tego nie popierają, jest to nieprzyjemne, ale nie katastroficzne.

Rozważmy teraz alternatywę. Gdyby UTF-16 nie istniał, nie mielibyśmy kodowania, które byłoby odpowiednie dla tekstu spoza ASCII, a całe oprogramowanie stworzone dla UCS-2 musiałoby zostać całkowicie przeprojektowane, aby pozostało zgodne z Unicode. Ten ostatni najprawdopodobniej spowolniłby przyjęcie Unicode. Również nie bylibyśmy w stanie utrzymać zgodności z tekstem w UCS-2, tak jak UTF-8 w stosunku do ASCII.

Odkładając na bok wszystkie starsze kwestie, jakie są argumenty przeciwko samemu kodowaniu? Naprawdę wątpię, aby programiści nie wiedzieli, że UTF-16 ma zmienną długość, jest napisany wszędzie, zaczynając od Wikipedii. UTF-16 jest znacznie trudniejszy do przeanalizowania niż UTF-8, jeśli ktoś wskazał złożoność jako możliwy problem. Błędem jest również sądzić, że łatwo jest zepsuć określenie długości łańcucha tylko w UTF-16. Jeśli używasz UTF-8 lub UTF-32, nadal powinieneś mieć świadomość, że jeden punkt kodowy Unicode niekoniecznie oznacza jeden znak. Poza tym nie sądzę, aby było coś istotnego przeciwko kodowaniu.

Dlatego nie sądzę, aby samo kodowanie było uważane za szkodliwe. UTF-16 to kompromis między prostotą a kompaktowością, a korzystanie z tego, co jest potrzebne, tam, gdzie jest to potrzebne , nie szkodzi . W niektórych przypadkach musisz pozostać kompatybilny z ASCII i potrzebujesz UTF-8, w niektórych przypadkach chcesz pracować z ideografami Hana i oszczędzać miejsce za pomocą UTF-16, w niektórych przypadkach potrzebujesz uniwersalnych reprezentacji znaków, które ustalają stałe- kodowanie długości. Użyj tego, co bardziej odpowiednie, po prostu zrób to poprawnie.

Malcolm
źródło
21
To raczej mrugający, anglo-centralny pogląd, Malcolm. Niemal na równi z „ASCII jest wystarczająco dobry dla USA - reszta świata powinna się z nami zmieścić”.
Jonathan Leffler
28
W rzeczywistości jestem z Rosji i cały czas spotykam się z cyryliką (w tym z własnymi programami), więc nie sądzę, że mam anglo-centryczny pogląd. :) Wspomnienie o ASCII nie jest do końca odpowiednie, ponieważ nie jest to Unicode i nie obsługuje określonych znaków. UTF-8, UTF-16, UTF-32 obsługują te same międzynarodowe zestawy znaków, są one przeznaczone tylko do użycia w określonych obszarach. I właśnie o to mi chodzi: jeśli używasz głównie angielskiego, użyj UTF-8, jeśli używasz głównie cyrylicy, użyj UTF-16, jeśli używasz starożytnych języków, użyj UTF-32. Całkiem proste.
Malcolm
16
„Nieprawda, skrypty azjatyckie, takie jak japoński, chiński lub arabski, również należą do BMP. Sam BMP jest w rzeczywistości bardzo duży i na pewno wystarczająco duży, aby uwzględnić wszystkie używane obecnie skrypty”. To wszystko jest tak źle. BMP zawiera 0xFFFF znaków (65536). Chińczycy mają więcej. Chińskie standardy (GB 18030) mają więcej. Unicode 5.1 już przydzielił ponad 100 000 znaków.
12
@Marcolm: „Sam BMP jest w rzeczywistości bardzo duży i na pewno wystarczająco duży, aby uwzględnić wszystkie używane obecnie skrypty” Nieprawda. W tym momencie Unicode przydzielił już około 100 000 znaków, znacznie więcej niż BMP może pomieścić. Poza BMP istnieją duże fragmenty chińskich znaków. Niektóre z nich są wymagane przez GB-18030 (obowiązkowy chiński standard). Inne są wymagane przez (nieobowiązkowe) standardy japońskie i koreańskie. Więc jeśli spróbujesz coś sprzedać na tych rynkach, potrzebujesz wsparcia poza BMP.
8
Wszystko, co używa UTF-16, ale może obsługiwać tylko wąskie znaki BMP, tak naprawdę nie używa UTF-16. Jest wadliwy i zepsuty. Założeniem OP jest solidne: UTF-16 jest szkodliwy, ponieważ prowadzi naiwnych ludzi do pisania złamanego kodu. Albo możesz obsłużyć tekst Unicode, albo nie możesz. Jeśli nie możesz, to wybierasz podzbiór, który jest tak samo głupi jak przetwarzanie tekstu tylko ASCII.
tchrist
16

Lata internacjonalizacji systemu Windows, szczególnie w językach Azji Wschodniej, mogły mnie zepsuć, ale skłaniam się ku UTF-16 do wewnętrznych reprezentacji ciągów w programie i UTF-8 do przechowywania w sieci lub plikach dokumentów w postaci zwykłego tekstu. UTF-16 można zwykle przetwarzać szybciej w systemie Windows, więc jest to podstawowa zaleta korzystania z UTF-16 w systemie Windows.

Skok do UTF-16 znacznie poprawił adekwatność przeciętnych produktów obsługujących tekst międzynarodowy. Jest tylko kilka wąskich przypadków, w których należy wziąć pod uwagę pary zastępcze (zasadniczo usunięcia, wstawienia i łamanie linii), a przeciętny przypadek jest przeważnie prosty. I w przeciwieństwie do wcześniejszych kodowań, takich jak warianty JIS, UTF-16 ogranicza pary zastępcze do bardzo wąskiego zakresu, więc sprawdzenie jest naprawdę szybkie i działa do przodu i do tyłu.

To prawda, że ​​jest również mniej więcej tak szybki w poprawnie zakodowanym UTF-8. Ale jest też wiele uszkodzonych aplikacji UTF-8, które niepoprawnie kodują pary zastępcze jako dwie sekwencje UTF-8. UTF-8 nie gwarantuje też zbawienia.

IE radzi sobie dość dobrze z parami zastępczymi od 2000 r., Mimo że zazwyczaj konwertuje je ze stron UTF-8 na wewnętrzną reprezentację UTF-16; Jestem całkiem pewien, że Firefox ma to również dobrze, więc tak naprawdę nie dbam o to, co robi Opera.

UTF-32 (znany również jako UCS4) jest bezcelowy dla większości aplikacji, ponieważ zajmuje tak mało miejsca, więc jest prawie niestabilny.

JasonTrue
źródło
6
Nie całkiem dostałem twój komentarz na temat UTF-8 i par zastępczych. Pary zastępcze to tylko koncepcja, która ma znaczenie w kodowaniu UTF-16, prawda? Być może kod, który konwertuje bezpośrednio z kodowania UTF-16 na kodowanie UTF-8, może to zrobić źle, i w takim przypadku problemem jest niepoprawny odczyt UTF-16, a nie zapisywanie UTF-8. Czy to prawda?
Craig McQueen
11
Jason mówi o oprogramowaniu, które celowo implementuje UTF-8 w ten sposób: utwórz parę zastępczą, a następnie UTF-8 koduj każdą połowę osobno. Prawidłowa nazwa tego kodowania to CESU-8, ale Oracle (np.) Błędnie przedstawia go jako UTF-8. Java stosuje podobny schemat do serializacji obiektów, ale jest wyraźnie udokumentowany jako „Zmodyfikowany UTF-8” i tylko do użytku wewnętrznego. (Teraz, jeśli moglibyśmy po prostu zachęcić ludzi do CZYTANIA tej dokumentacji i przestać używać DataInputStream # readUTF () i DataOutputStream # writeUTF () niewłaściwie ...)
AFAIK, UTF-32 jest wciąż kodowaniem o zmiennej długości i nie jest równy UCS4, który jest specyficznym zakresem punktu kodowego.
Eonil,
@Eonil, UTF-32 będzie można odróżnić od UCS4 tylko wtedy, gdy będziemy mieli standard Unicode, który zawiera coś w rodzaju UCS5 lub większego.
JasonTrue
@JasonTrue Nadal tylko wyniki są równe przypadkowo, nie są gwarantowane przez projekt. To samo stało się w 32-bitowym adresowaniu pamięci, Y2K, UTF16 / UCS2. Czy mamy gwarancję tej równości? Jeśli tak, chętnie bym tego użył. Ale nie chcę pisać możliwego do złamania kodu. Piszę kod na poziomie znaków, a brak gwarantowanego sposobu transkodowania między punktem kodowym UTF <-> bardzo mnie wkurza.
Eonil
16

UTF-8 jest zdecydowanie najlepszą drogą, być może towarzyszy mu UTF-32 do użytku wewnętrznego w algorytmach wymagających wysokiej wydajności dostępu losowego (ale ignoruje łączenie znaków).

Zarówno UTF-16, jak i UTF-32 (jak również ich warianty LE / BE) cierpią na problemy związane z endianizmem, dlatego nigdy nie należy ich używać zewnętrznie.

Tronic
źródło
9
UTF-8 ma również dostęp losowy w stałym czasie, wystarczy użyć jednostek kodowych zamiast punktów kodowych. Być może potrzebujesz prawdziwego losowego dostępu do punktu kodowego, ale nigdy nie widziałem przypadku użycia i równie prawdopodobne jest, że będziesz chciał dostępu do losowego dostępu do klastra grafemowego.
15

UTF-16? zdecydowanie szkodliwe. Tylko moje ziarno soli tutaj, ale istnieją dokładnie trzy dopuszczalne kodowania tekstu w programie:

  • ASCII: w przypadku rzeczy niskiego poziomu (np. Mikrokontrolerów), na które nie stać nic lepszego
  • UTF8: przechowywanie na nośnikach o stałej szerokości, takich jak pliki
  • integer codepoints („CP”?): tablica największych liczb całkowitych, które są wygodne dla twojego języka programowania i platformy (rozpada się na ASCII w limicie niskich rezystancji). Powinien być int32 na starszych komputerach i int64 na cokolwiek z adresowaniem 64-bitowym.

  • Oczywiście interfejsy do starszego kodu używają tego, co jest potrzebne do poprawnego działania starego kodu.

David X
źródło
4
@simon buchan, U+10ffffmaksimum wyjdzie przez okno, gdy (nie jeśli) zabraknie im współrzędnych kodowych . To powiedziawszy, użycie int32 w systemie p64 dla prędkości jest prawdopodobnie bezpieczne, ponieważ wątpię, aby przekroczyły, U+ffffffffzanim będziesz zmuszony przepisać kod dla systemów 128-bitowych około 2050 roku. jest wygodny ”w przeciwieństwie do„ największego dostępnego ”(który prawdopodobnie byłby int256 lub bignum czy coś takiego).)
David X
1
@David: Unicode 5.2 koduje 107 361 współrzędnych kodowych. Jest 867,169 nieużywanych współrzędnych kodowych. „kiedy” jest po prostu głupie. Punkt kodowy Unicode jest zdefiniowany jako liczba od 0 do 0x10FFFF, właściwość, od której zależy UTF-16. (Także rok 2050 wydaje się zbyt niski w przypadku systemów 128-bitowych, gdy system 64-bitowy może pomieścić cały Internet w swojej przestrzeni adresowej.)
3
@David: Twoje „kiedy” odnosiło się do wyczerpania się punktów kodowych Unicode, a nie 128-bitowego przełącznika, który, tak, będzie w ciągu kilku następnych stuleci. W przeciwieństwie do pamięci, nie ma wykładniczego wzrostu liczby znaków, więc Konsorcjum Unicode ma szczególną gwarancję, że nigdy nie przydzieli powyższego punktu kodowego U+10FFFF. To naprawdę jedna z tych sytuacji, gdy 21 bitów jest wystarczające dla każdego.
10
@Simon Buchan: Przynajmniej do pierwszego kontaktu. :)
3
Unicode gwarantuje, że nie będzie też punktów kodowych powyżej U + FFFF.
Shannon Severance
13

Unicode definiuje punkty kodu do 0x10FFFF (1114112 kodów), wszystkie aplikacje działające w środowisku wielojęzycznym zajmującym się łańcuchami / nazwami plików itp. Powinny to poprawnie obsługiwać.

Utf-16 : obejmuje tylko 1.112.064 kodów. Chociaż te na końcu Unicode pochodzą z samolotów 15-16 (obszar prywatnego użytku). Nie może dalej rosnąć w przyszłości, z wyjątkiem złamania koncepcji Utf-16 .

Utf-8 : obejmuje teoretycznie 2 216 757 376 kodów. Aktualny zakres kodów Unicode może być reprezentowany przez maksymalnie 4 bajty. Nie ma problemu z kolejnością bajtów , jest „kompatybilny” z ascii.

Utf-32 : obejmuje teoretycznie 2 ^ 32 = 4 294 967 296 kodów. Obecnie nie jest kodowany o zmiennej długości i prawdopodobnie nie będzie w przyszłości.

Te fakty są oczywiste. Nie rozumiem zalecania ogólnego używania Utf-16 . Jest kodowany o zmiennej długości (nie jest dostępny za pomocą indeksu), ma problemy z pokryciem całego zakresu Unicode nawet w tej chwili, kolejność bajtów musi być obsługiwana itp. Nie widzę żadnej korzyści poza tym, że jest natywnie używany w systemie Windows i niektórych inne miejsca. Chociaż podczas pisania kodu na wielu platformach prawdopodobnie lepiej jest używać Utf-8 natywnie i dokonywać konwersji tylko w punktach końcowych w sposób zależny od platformy (jak już sugerowano). Gdy niezbędny jest bezpośredni dostęp do indeksu, a pamięć nie stanowi problemu, należy użyć Utf-32 .

Głównym problemem jest to, że wielu programistów zajmujących się Windows Unicode = Utf-16 nawet nie wie ani nie ignoruje faktu, że jest on zakodowany o zmiennej długości.

Sposób, w jaki zwykle jest na platformie * nix , jest całkiem niezły: łańcuchy c (char *) interpretowane jako zakodowane w Utf-8 , szerokie łańcuchy c (wchar_t *) interpretowane jako Utf-32 .

Pavel Machyniak
źródło
7
Uwaga: UTF-16 obejmuje wszystkie Unicode, ponieważ konsorcjum Unicode zdecydowało, że 10FFFF jest NAJWYŻSZYM zakresem Unicode i zdefiniowało maksymalną długość 4 bajtów UTF-8 i wyraźnie wykluczono zakres 0xD800-0xDFFF z prawidłowego zakresu punktów kodowych i ten zakres jest używany do tworzenia pary zastępcze. Tak więc każdy prawidłowy tekst Unicode może być reprezentowany za pomocą każdego z tych kodowań. Także o dojściu do przyszłości. Nie wydaje się, że 1 milion punktów kodowych nie wystarczyłby w dalekiej przyszłości.
7
@Kerrek: Niepoprawnie: UCS-2 nie jest prawidłowym kodowaniem Unicode. Wszystkie kodowania UTF- * z definicji mogą reprezentować dowolny punkt kodowy Unicode, który jest legalny do wymiany. UCS-2 może reprezentować znacznie mniej, plus kilka więcej. Powtórz: UCS-2 nie jest prawidłowym kodowaniem Unicode, jakkolwiek więcej niż ASCII.
tchrist
1
„Nie rozumiem zalecania ogólnego zastosowania Utf-8 . Jest on zakodowany w zmiennej długości (nie można uzyskać do niego dostępu za pomocą indeksu)”
Ian Boyd
9
@Ian Boyd, potrzeba dostępu do indywidualnej postaci łańcucha w losowym wzorze dostępu jest niezwykle zawyżona. Jest to tak powszechne, jak chęć obliczenia przekątnej matrycy znaków, co jest bardzo rzadkie. Ciągi są praktycznie zawsze przetwarzane sekwencyjnie, a ponieważ dostęp do UTF-8 char N + 1, biorąc pod uwagę, że masz UTF-8 char N, to O (1), nie ma problemu. Niezwykle mała jest potrzeba losowego dostępu do łańcuchów. Niezależnie od tego, czy uważasz, że warto przejść do UTF-32 zamiast UTF-8, to Twoja własna opinia, ale dla mnie to w ogóle nie problem.
tchrist
2
@ tchrist, dam ci, że ciągi są praktycznie zawsze przetwarzane sekwencyjnie, jeśli uwzględnisz odwrotną iterację jako „sekwencyjną” i rozciągniesz to, aby trochę dalej porównać końcowy koniec łańcucha ze znanym ciągiem. Dwa bardzo popularne scenariusze to obcinanie białych znaków na końcu ciągów i sprawdzanie rozszerzenia pliku na końcu ścieżki.
Andy Dent
11

Dodaj to do listy:

Przedstawiony scenariusz jest prosty (jeszcze prostszy, ponieważ przedstawię go tutaj niż pierwotnie!): 1. WinForm TextBox siedzi na formularzu, pusty. Ma maksymalną długość ustawioną na 20 .

2. Użytkownik pisze w TextBox, a może wkleja do niego tekst.

3. Bez względu na to, co wpiszesz lub wkleisz w TextBox, jesteś ograniczony do 20, chociaż będzie on sympatycznie wydawał dźwięk przy tekście wykraczającym poza 20 (tutaj YMMV; zmieniłem mój schemat dźwiękowy, aby dać mi ten efekt!).

4. Mały pakiet tekstu jest następnie wysyłany gdzie indziej, aby rozpocząć ekscytującą przygodę.

Jest to łatwy scenariusz i każdy może to napisać w wolnym czasie. Właśnie napisałem to w wielu językach programowania przy użyciu WinForm, ponieważ byłem znudzony i nigdy wcześniej tego nie próbowałem. I z tekstem w wielu rzeczywistych językach, ponieważ jestem w ten sposób podłączony i mam więcej układów klawiatury niż jakikolwiek inny w całym dziwacznym wszechświecie.

Nazwałem nawet nazwę Magic Carpet Ride , aby złagodzić nudę.

To nie zadziałało, bo warto.

Zamiast tego wprowadziłem następujące 20 znaków do formularza Magic Carpet Ride :

0123401234012340123 𠀀

O o.

Ostatnią postacią jest U + 20000, pierwszy ideogram rozszerzenia B Unicode (inaczej U + d840 U + dc00, dla jego bliskich przyjaciół, którym nie wstydzi się zostać rozbrojonym przed ...)

wprowadź opis zdjęcia tutaj

A teraz mamy grę w piłkę.

Ponieważ kiedy mówi TextBox.MaxLength

Pobiera lub ustawia maksymalną liczbę znaków, które można ręcznie wprowadzić w polu tekstowym.

tak naprawdę to znaczy

Pobiera lub ustawia maksymalną liczbę jednostek kodu UTF-16 LE, które można ręcznie wprowadzić do pola tekstowego i bezlitośnie obcinają żywe bzdury z dowolnego ciągu znaków, który próbuje grać w urocze gry z lingwistycznym charakterem, że tylko osoba obsesyjna jak ten Kaplan uzna za obraźliwy (rany, on musi wydostać się więcej!).

Postaram się zaktualizować dokument ...
Zwykli czytelnicy, którzy pamiętają moją serię UCS-2 do UTF-16, zauważą moje niezadowolenie z uproszczonego pojęcia TextBox.MaxLength i tego, jak powinien sobie z tym poradzić co najmniej w tym przypadku gdzie jego drakońskie zachowanie tworzy nielegalną sekwencję, taką, którą inne części systemu .Net Framework mogą wygenerować

  • System.Text.EncoderFallbackException: Nie można przetłumaczyć znaku Unicode \ oD850 o indeksie 0 na określoną stronę kodową. *

wyjątek, jeśli przekażesz ten ciąg w innym miejscu .NET Framework (jak robił to mój kolega Dan Thompson).

Teraz dobrze, być może pełna seria UCS-2 do UTF-16 jest poza zasięgiem wielu.
Ale czy nie jest rozsądne oczekiwać, że TextBox.Text nie wygeneruje System.Stringnie spowoduje to rzucenia innego elementu .NET Framework? To znaczy, nie jest tak, że istnieje szansa w postaci jakiegoś zdarzenia w sterowaniu, które mówi o zbliżającym się skróceniu, w którym można łatwo dodać mądrzejszą weryfikację - weryfikację, której sama kontrola nie ma nic przeciwko. Posunąłbym się nawet do stwierdzenia, że ​​ta punkowa kontrola łamie umowę bezpieczeństwa, która może nawet prowadzić do problemów bezpieczeństwa, jeśli można zaklasyfikować, powodując nieoczekiwane wyjątki, aby zakończyć aplikację jako prymitywny rodzaj odmowy usługi. Dlaczego jakikolwiek proces, metoda, algorytm lub technika WinForms powinny dawać nieprawidłowe wyniki?

Źródło: Michael S. Kaplan Blog MSDN

Matthieu
źródło
Dzięki, bardzo dobry link! Dodałem go do listy problemów w pytaniu.
9

Niekoniecznie powiedziałbym, że UTF-16 jest szkodliwy. Nie jest elegancki, ale służy jego kompatybilności wstecznej z UCS-2, podobnie jak GB18030 robi z GB2312, a UTF-8 z ASCII.

Ale wprowadzenie fundamentalnej zmiany w strukturze Unicode w środkowej fazie, po tym jak Microsoft i Sun zbudowały ogromne interfejsy API wokół znaków 16-bitowych, było szkodliwe. Niepowodzenie w rozpowszechnianiu świadomości zmiany było bardziej szkodliwe.

dan04
źródło
8
UTF-8 jest nadzbiorem ASCII, ale UTF-16 NIE jest nadzbiorem UCS-2. Chociaż prawie nadzbiór, prawidłowe kodowanie UCS-2 w UTF-8 powoduje ohydę znaną jako CESU-8; UCS-2 nie ma odpowiedników, tylko zwykłe punkty kodowe, więc muszą być jako takie przetłumaczone. Prawdziwą zaletą UTF-16 jest to, że łatwiej jest zaktualizować bazę kodów UCS-2 niż całkowite przepisanie UTF-8. Śmieszne, co?
1
Jasne, technicznie UTF-16 nie jest nadzbiorem UCS-2, ale kiedy U + D800 do U + DFFF były kiedykolwiek używane do czegoś innego niż surogaty UTF-16?
dan04,
2
Nie ma znaczenia Każde przetwarzanie inne niż ślepe przechodzenie przez strumień boczny wymaga odkodowania par zastępczych, czego nie można zrobić, jeśli traktuje się je jako UCS-2.
6

UTF-16 jest najlepszym kompromisem między obsługą i przestrzenią i dlatego większość głównych platform (Win32, Java, .NET) używa go do wewnętrznej reprezentacji ciągów.

Nemanja Trifunovic
źródło
31
-1, ponieważ UTF-8 może być mniejszy lub nieznacznie inny. W przypadku niektórych skryptów azjatyckich UTF-8 ma trzy bajty na glif, podczas gdy UTF-16 ma tylko dwa, ale równoważy to fakt, że UTF-8 jest tylko jednym bajtem dla ASCII (co często pojawia się nawet w językach azjatyckich w nazwach produktów, poleceniach itp. rzeczy). Ponadto we wspomnianych językach glif przekazuje więcej informacji niż znak łaciński, dlatego uzasadnione jest, aby zajmował więcej miejsca.
32
Nie nazwałbym łączenia najgorszych stron obu opcji dobrym kompromisem.
18
To nie jest łatwiejsze niż UTF-8. Ma również zmienną długość.
luiscubal
36
Odkładając na bok debaty na temat zalet UTF-16: to, co zacytowałeś, nie jest powodem korzystania z UTF-16 przez Windows, Javę lub .NET. Windows i Java pochodzą z czasów, gdy Unicode było kodowaniem 16-bitowym. UCS-2 był wówczas rozsądnym wyborem. Kiedy Unicode stało się 21-bitowym kodowaniem, migracja do UTF-16 była najlepszym wyborem dla istniejących platform. Nie miało to nic wspólnego z łatwością obsługi lub kompromisami przestrzennymi. To tylko kwestia dziedzictwa.
Joey,
10
.NET dziedziczy tutaj dziedzictwo systemu Windows.
Joey,
6

Nigdy nie zrozumiałem sensu UTF-16. Jeśli chcesz uzyskać najbardziej oszczędną przestrzennie reprezentację, użyj UTF-8. Jeśli chcesz traktować tekst jako tekst o stałej długości, użyj UTF-32. Jeśli nie chcesz, użyj UTF-16. Co gorsza, ponieważ wszystkie typowe (podstawowe płaszczyzny wielojęzyczne) znaki w UTF-16 mieszczą się w jednym punkcie kodu, błędy, które zakładają, że UTF-16 ma stałą długość, będą subtelne i trudne do znalezienia, natomiast jeśli spróbujesz to zrobić w przypadku UTF-8 Twój kod szybko i głośno zawiedzie, gdy tylko spróbujesz internacjonalizować.

dsimcha
źródło
6

Ponieważ nie mogę jeszcze komentować, zamieszczam to jako odpowiedź, ponieważ wydaje się, że inaczej nie mogę skontaktować się z autorami utf8everywhere.org. Szkoda, że ​​nie dostaję automatycznie uprawnienia do komentowania, ponieważ mam wystarczającą reputację na innych zmianach stosów.

Ma to stanowić komentarz do opinii: Tak, UTF-16 należy uznać za szkodliwą odpowiedź.

Jedna mała korekta:

Aby zapobiec przypadkowemu przekazaniu UTF-8 char*do wersji ANSI-string funkcji Windows-API, należy to zdefiniować UNICODE, a nie _UNICODE. _UNICODEFunkcje Mapy podobne _tcslendo wcslen, nie MessageBoxdo MessageBoxW. Zamiast tego UNICODEdefiniuje to drugie. Na dowód pochodzi z WinUser.hnagłówka MS Visual Studio 2005 :

#ifdef UNICODE
#define MessageBox  MessageBoxW
#else
#define MessageBox  MessageBoxA
#endif // !UNICODE

Błąd ten powinien zostać co najmniej naprawiony utf8everywhere.org.

Sugestia:

Być może przewodnik powinien zawierać przykład jawnego użycia szerokiej struny wersji struktury danych, aby ułatwić jej pominięcie / zapomnienie. Używanie wersji struktur danych z szerokim łańcuchem oraz stosowanie wersji funkcji z szerokimi łańcuchami sprawia, że ​​jeszcze mniej prawdopodobne jest, że przypadkowo wywoła się taką wersję funkcji w wersji ANSI.

Przykład przykładu:

WIN32_FIND_DATAW data; // Note the W at the end.
HANDLE hSearch = FindFirstFileW(widen("*.txt").c_str(), &data);
if (hSearch != INVALID_HANDLE_VALUE)
{
    FindClose(hSearch);
    MessageBoxW(nullptr, data.cFileName, nullptr, MB_OK);
}
Jelle Geerts
źródło
Zgoda; dzięki! Zaktualizujemy dokument. Dokument wciąż wymaga dalszego rozwoju i dodawania informacji o bazach danych. Chętnie otrzymamy wkład sformułowań.
Pavel Radzivilovsky
@PavelRadzivilovsky _UNICODEwciąż tam jest :(
cubuspl42
dzięki za przypomnienie. cubus, Jelle, Czy chciałbyś użytkownika do naszej SVN?
Pavel Radzivilovsky
@Pavel Pewnie, doceniłbym to!
Jelle Geerts,
@JelleGeerts: przepraszam za to opóźnienie. Zawsze możesz skontaktować się z nami za pośrednictwem naszych e-maili (powiązanych z manifestem) lub Facebooka. Łatwo nas znaleźć. Chociaż uważam, że naprawiliśmy problem, który tu przyniosłeś (i przypisałem ci to), całe debaty UTF-8 kontra UTF-16 są nadal aktualne. Jeśli masz więcej do powiedzenia, skontaktuj się z nami za pośrednictwem tych prywatnych kanałów.
ybungalobill
5

Ktoś powiedział, że UCS4 i UTF-32 były takie same. Nie, ale wiem, co masz na myśli. Jedno z nich jest jednak kodowaniem drugiego. Żałuję, że nie zastanawiali się nad określeniem endianizmu od pierwszego, aby nie toczyła się tutaj również bitwa endianess. Czy nie widzieli, że to nadchodzi? Przynajmniej UTF-8 jest wszędzie taki sam (chyba że ktoś przestrzega oryginalnej specyfikacji z 6 bajtami).

Jeśli używasz UTF-16, musisz uwzględnić obsługę znaków wielobajtowych. Nie możesz przejść do n-tego znaku, indeksując 2N w tablicy bajtów. Musisz go przejść lub mieć indeksy postaci. W przeciwnym razie napisałeś błąd.

Obecna wersja robocza specyfikacji C ++ mówi, że UTF-32 i UTF-16 mogą mieć warianty little-endian, big-endian i nieokreślone. Naprawdę? Gdyby Unicode określił, że wszyscy muszą robić little-endian od samego początku, wszystko byłoby prostsze. (Byłbym w porządku z big-endianem.) Zamiast tego, niektórzy ludzie wdrożyli to w jeden sposób, inni w inny, a teraz utknęliśmy w głupocie za nic. Czasami zawód inżyniera oprogramowania jest krępujący.

user22815
źródło
Nieokreślona endianiczność ma zawierać BOM jako pierwszy znak, używany do określenia, w jaki sposób należy odczytać ciąg. UCS-4 i UTF-32 rzeczywiście są obecnie takie same, tj. Liczbowa wartość UCS od 0 do 0x10FFFF przechowywana w 32-bitowej liczbie całkowitej.
5
@Tronic: Technicznie nie jest to prawdą. Chociaż UCS-4 może przechowywać dowolną 32-bitową liczbę całkowitą, UTF-32 nie może przechowywać znaków innych niż znaki kodowe, które są nielegalne dla wymiany, takich jak 0xFFFF, 0xFFFE i wszystkie zastępcze wartości. UTF jest kodowaniem transportowym, a nie wewnętrznym.
tchrist
Kwestie endianizmu są nieuniknione, dopóki różne procesory nadal używają różnych kolejności bajtów. Byłoby jednak dobrze, gdyby istniała „preferowana” kolejność bajtów do przechowywania plików UTF-16.
Qwertie
Mimo że UTF-32 ma stałą szerokość dla punktów kodowych , nie ma stałej szerokości dla znaków . (Słyszałeś o czymś zwanym „łączeniem znaków”?) Więc nie możesz przejść do N-tej postaci, po prostu indeksując 4N do tablicy bajtów.
musiphil
2

Nie uważam, że to szkodliwe, jeśli deweloper jest wystarczająco ostrożny.
I powinni zaakceptować tę kompromis, jeśli też dobrze wiedzą.

Jako japoński programista uważam, że UCS-2 jest wystarczająco duży, a ograniczenie przestrzeni najwyraźniej upraszcza logikę i zmniejsza pamięć środowiska wykonawczego, więc używanie utf-16 w ramach ograniczeń UCS-2 jest wystarczająco dobre.

Istnieje system plików lub inna aplikacja, która zakłada, że ​​punkty kodowe i bajty są proporcjonalne, dzięki czemu można zagwarantować, że nieprzetworzona liczba punktów kodowych będzie pasować do pamięci o ustalonym rozmiarze.

Jednym z przykładów jest NTFS i VFAT określające UCS-2 jako kodowanie pamięci plików.

Jeśli ten przykład naprawdę chce rozszerzyć się o obsługę UCS-4, i tak mógłbym zgodzić się na użycie utf-8 do wszystkiego, ale stała długość ma takie zalety, jak:

  1. może zagwarantować rozmiar według długości (rozmiar danych i długość kodu jest proporcjonalna)
  2. może użyć numeru kodowego do wyszukiwania skrótów
  3. nieskompresowane dane mają rozsądny rozmiar (w porównaniu do utf-32 / UCS-4)

W przyszłości, gdy moc pamięci / przetwarzania będzie tania, nawet w jakichkolwiek urządzeniach wbudowanych, możemy zaakceptować, że urządzenie jest nieco wolne z powodu dodatkowych błędów pamięci podręcznej lub błędów stron i dodatkowego użycia pamięci, ale chyba nie nastąpi to w najbliższej przyszłości ...

holmes
źródło
3
Dla osób czytających ten komentarz warto zauważyć, że UCS-2 to nie to samo, co UTF-16. Sprawdź różnice, aby je zrozumieć.
mikebabcock,
1

„Czy jedno z najpopularniejszych kodowań, UTF-16, należy uznać za szkodliwe?”

Całkiem możliwe, ale alternatywy niekoniecznie powinny być postrzegane jako znacznie lepsze.

Podstawową kwestią jest to, że istnieje wiele różnych koncepcji na temat: glifów, znaków, punktów kodowych i sekwencji bajtów. Odwzorowanie między nimi jest nietrywialne, nawet przy pomocy biblioteki normalizacyjnej. (Na przykład niektóre znaki w językach europejskich, które są napisane skryptem łacińskim, nie są pisane pojedynczym kodem Unicode. I to jest na prostszym końcu złożoności!) Oznacza to, że uzyskanie poprawności jest dość zadziwiające trudny; dziwnych błędów należy się spodziewać (i zamiast po prostu narzekać na ich temat, powiedz opiekunom danego oprogramowania).

Jedynym sposobem, w jaki UTF-16 może być uważany za szkodliwy w przeciwieństwie do, powiedzmy, UTF-8, jest inny sposób kodowania punktów kodowych poza BMP (jako para surogatów). Jeśli kod chce uzyskać dostęp lub iterować według kodu, oznacza to, że musi być świadomy różnicy. OTOH, oznacza to, że znaczna część istniejącego kodu, który zakłada „znaki”, zawsze może być dopasowana do dwubajtowej liczby - dość powszechne, jeśli błędne założenie - może przynajmniej kontynuować pracę bez odbudowywania wszystkiego. Innymi słowy, przynajmniej zobaczysz te postacie, które nie są odpowiednio traktowane!

Odwróciłbym twoje pytanie do głowy i powiedziałbym, że cały ten przeklęty Unicode powinien być uważany za szkodliwy i każdy powinien używać kodowania 8-bitowego, z wyjątkiem tego, co widziałem (w ciągu ostatnich 20 lat), gdzie to prowadzi: okropne zamieszanie związane z różnymi kodowaniami ISO 8859, a także z całym zestawem kodowań używanych w cyrylicy i pakietem EBCDIC oraz… no cóż, Unicode dla wszystkich jego błędów pokonuje to. Gdyby to nie był tak paskudny kompromis między nieporozumieniami różnych krajów.

Donal Fellows
źródło
Znając nasze szczęście, za kilka lat zabraknie nam miejsca w UTF-16. Meh
Donal Fellows
3
Podstawową kwestią jest to, że tekst jest zwodniczo trudny. Żadne podejście do reprezentowania tych informacji w sposób cyfrowy nie może być nieskomplikowane. To ten sam powód, dla którego daty są trudne, kalendarze są trudne, czas jest trudny, imiona są trudne, adresy pocztowe są trudne: gdy maszyny cyfrowe krzyżują się z ludzkimi konstruktami kulturowymi, wybucha złożoność. To fakt z życia. Ludzie nie działają na logice cyfrowej.
Arystoteles Pagaltzis