Czy UTF-16 ma stałą szerokość czy zmienną szerokość? Dlaczego UTF-8 nie ma problemu z kolejnością bajtów?

16
  1. Czy UTF-16 ma stałą szerokość czy zmienną szerokość? Otrzymałem różne wyniki z różnych źródeł:

    From http://www.tbray.org/ongoing/When/200x/2003/04/26/UTF :

    UTF-16 przechowuje znaki Unicode w szesnastobitowych porcjach.

    From http://en.wikipedia.org/wiki/UTF-16/UCS-2 :

    UTF-16 (16-bitowy format transformacji Unicode) to kodowanie znaków dla Unicode, zdolne do kodowania 1 112 064 [1] liczb (zwanych punktami kodowymi) w przestrzeni kodu Unicode od 0 do 0x10FFFF. Daje wynik o zmiennej długości jednej lub dwóch 16-bitowych jednostek kodu na punkt kodowy.

  2. Z pierwszego źródła

    Zaletą UTF-8 jest to, że jednostką kodowania jest bajt, więc nie występują problemy z kolejnością bajtów.

    Dlaczego UTF-8 nie ma problemu z kolejnością bajtów? Ma zmienną szerokość, a jeden znak może zawierać więcej niż jeden bajt, więc myślę, że kolejność bajtów nadal może stanowić problem?

Dziękuję i pozdrawiam!

StackExchange dla wszystkich
źródło

Odpowiedzi:

13

(1) Co oznacza sekwencja bajtów, arrary char w C? Czy UTF-16 jest sekwencją bajtów lub co to jest? (2) Dlaczego sekwencja bajtów nie ma nic wspólnego ze zmienną długością?

Wygląda na to, że nie rozumiesz, jakie są problemy związane z endianem. Oto krótkie podsumowanie.

32-bitowa liczba całkowita zajmuje 4 bajty. Teraz znamy logiczną kolejność tych bajtów. Jeśli masz 32-bitową liczbę całkowitą, możesz uzyskać jej wyższy bajt za pomocą następującego kodu:

uint32_t value = 0x8100FF32;
uint8_t highByte = (uint8_t)((value >> 24) & 0xFF); //Now contains 0x81

Wszystko dobrze i dobrze. Problem zaczyna się od tego, jak różne urządzenia zapisują i pobierają liczby całkowite z pamięci.

W kolejności Big Endian 4 bajtowa pamięć, którą odczytujesz jako 32-bitową liczbę całkowitą, zostanie odczytana, a pierwszy bajt będzie bajtem najwyższym:

[0][1][2][3]

W kolejności Little Endian 4-bajtowa pamięć, którą odczytujesz jako 32-bitową liczbę całkowitą, zostanie odczytana, przy czym pierwszy bajt będzie bajtem niższym :

[3][2][1][0]

Jeśli masz wskaźnik do wskaźnika do wartości 32-bitowej, możesz to zrobić:

uint32_t value = 0x8100FF32;
uint32_t *pValue = &value;
uint8_t *pHighByte = (uint8_t*)pValue;
uint8_t highByte = pHighByte[0]; //Now contains... ?

Według C / C ++ wynik tego jest niezdefiniowany. Może to być 0x81. Lub może to być 0x32. Technicznie rzecz biorąc, może zwrócić wszystko, ale w przypadku prawdziwych systemów zwróci jedno lub drugie.

Jeśli masz wskaźnik do adresu pamięci, możesz odczytać ten adres jako wartość 32-bitową, 16-bitową lub 8-bitową. Na dużej maszynie Endian wskaźnik wskazuje na wysoki bajt; na małej maszynie Endian wskaźnik wskazuje na niski bajt.

Zauważ, że chodzi o czytanie i pisanie do / z pamięci. Nie ma to nic wspólnego z wewnętrznym kodem C / C ++. Pierwsza wersja kodu, ta, której C / C ++ nie deklaruje jako niezdefiniowany, zawsze będzie działać, aby uzyskać wysoki bajt.

Problem polega na tym, gdy zaczynasz czytać strumienie bajtów. Tak jak z pliku.

Wartości 16-bitowe mają te same problemy, co wartości 32-bitowe; mają po prostu 2 bajty zamiast 4. Dlatego plik może zawierać 16-bitowe wartości przechowywane w dużej lub małej kolejności.

UTF-16 jest zdefiniowany jako sekwencja 16-bitowych wartości . Skutecznie jest to uint16_t[]. Każda pojedyncza jednostka kodu ma wartość 16-bitową. Dlatego, aby poprawnie załadować UTF-16, musisz wiedzieć, co to jest endianność danych.

UTF-8 jest zdefiniowany jako ciąg wartości 8-bitowych . To jest uint8_t[]. Każda pojedyncza jednostka kodu ma 8 bitów: jeden bajt.

Teraz zarówno UTF-16, jak i UTF-8 pozwalają na połączenie wielu jednostek kodu (wartości 16-bitowe lub 8-bitowe) w celu utworzenia punktu kodowego Unicode („znak”, ale to nie jest poprawny termin; jest to uproszczenie ). Kolejność tych jednostek kodu, które tworzą kodowy jest podyktowane UTF-16 i UTF-8 kodowania.

Podczas przetwarzania UTF-16 odczytujesz 16-bitową wartość, robiąc wszystko, czego potrzeba konwersja endian. Następnie wykrywasz, czy jest to para zastępcza; jeśli tak, to odczytujesz kolejną 16-bitową wartość, łączysz obie, a następnie otrzymujesz wartość punktu kodowego Unicode.

Podczas przetwarzania UTF-8 odczytujesz wartość 8-bitową. Żadna konwersja endiana nie jest możliwa, ponieważ jest tylko jeden bajt. Jeśli pierwszy bajt oznacza sekwencję wielobajtową, to odczytujesz pewną liczbę bajtów, zgodnie z sekwencją wielobajtową. Każdy pojedynczy bajt jest bajtem i dlatego nie ma konwersji typu endian. Kolejność tych bitów w sekwencji, tak jak kolejność pary zastępczych UTF-16 jest określona przez UTF-8.

Tak więc nie może być żadnych problemów endian z UTF-8.

Nicol Bolas
źródło
10

Odpowiedź Jeremy Banks jest poprawna, ale nie dotyczyła kolejności bajtów.

Kiedy używasz UTF-16, większość glifów jest przechowywana przy użyciu dwubajtowego słowa - ale kiedy to słowo jest przechowywane w pliku dyskowym, w jakiej kolejności zapisujesz bajty składowe?

Na przykład glif CJK (chiński) dla słowa „woda” ma kodowanie UTF-16 w systemie szesnastkowym 6C34. Kiedy zapisujesz to jako dwa bajty na dysk, czy zapisujesz to jako „big-endian” (dwa bajty to 6C 34)? A może piszesz to jako „little-endian (dwa bajty to 34 6C)?

W przypadku UTF-16 oba porządki są uzasadnione i zwykle wskazuje się, które z nich ma plik, czyniąc pierwsze słowo w pliku znakiem Byte Order Mark (BOM), który dla kodowania big-endian to FE FF, a dla little-endian kodowanie to FF FE.

UTF-32 ma ten sam problem i to samo rozwiązanie.

UTF-8 nie ma tego problemu, ponieważ ma zmienną długość, a Ty efektywnie zapisujesz sekwencję bajtów glifów, jakby to był little-endian. Na przykład litera „P” jest zawsze kodowana przy użyciu jednego bajtu - 80 - a znak zastępujący jest zawsze kodowany przy użyciu dwóch bajtów FF FD w tej kolejności.

Niektóre programy umieszczają trzy bajtowy wskaźnik (EF BB BF) na początku pliku UTF-8, co pomaga odróżnić UTF-8 od podobnych kodowań, takich jak ASCII, ale nie jest to zbyt częste, z wyjątkiem MS Windows.

Bob Murphy
źródło
Dzięki! (1) litera „P” jest tylko jednym bajtem w UTF-8. Dlaczego znak zastępujący jest dodawany do jego kodu? (2) W UTF-8 istnieją inne znaki, które mają więcej niż jeden bajt w UTF-8. Dlaczego kolejność bajtów między bajtami dla każdego takiego znaku nie stanowi problemu?
StackExchange dla wszystkich
@Tim: (1) Nie dodajesz znaku zastępczego do kodu P. Jeśli widzisz 80 FF FD, to dwa znaki - znak P i znak zastępczy.
Bob Murphy,
(2) Zawsze piszesz i odczytujesz dwa bajty dla „znaku zastępującego” jako FF FD, w tej kolejności. Wystąpiłby tylko problem z kolejnością bajtów, gdybyś mógł również napisać „znak zastępczy” jako FD FF - ale nie możesz; ta sekwencja dwóch bajtów byłaby czymś innym niż „znakiem zastępczym”.
Bob Murphy,
1
@Tim: Możesz chcieć pracować przez en.wikipedia.org/wiki/UTF-8 . Jest naprawdę całkiem dobry, a jeśli rozumiesz wszystko i inne strony Wikipedii związane z Unicode, myślę, że nie będziesz mieć więcej pytań na ten temat.
Bob Murphy
4
Powodem, dla którego UTF-8 nie ma problemu z kolejnością bajtów, jest to, że kodowanie jest zdefiniowane jako sekwencja bajtów i że nie ma odmian o różnej endianowości. Nie ma to nic wspólnego ze zmienną długością.
starblue