UTF-16
UTF-16 jest 16-bitowym schematem kodowania o zmiennej długości i używa zestawu znaków UTF dla punktów kodu znaków. Oznacza to, że kodowany znak UTF-16 będzie miał 16-bitową jednostkę kodu.
ponieważ wiemy, że znak kodowany UTF-8 może być reprezentowany w 1 do 4 jednostkach kodu, znak UTF-16 może być reprezentowany w 1 lub 2 jednostkach kodu. W związku z tym znak UTF-16 może przyjmować 16 lub 32 bity pamięci w zależności od jego punktu kodowego.
zanim przejdziemy do specyfikacji kodowania UTF-16, zobaczmy, jak możemy sprawić, że UTF-16 będzie działać.
ponieważ mamy 16-bitową jednostkę kodu, teoretycznie możemy zakodować 21⁶ znaków od punktu kodu 0 do 65,535. Ale co, jeśli mamy znak o punkcie kodowym większym niż 65,535? W takim przypadku możemy dodać kolejną jednostkę kodu.
z dodatkową jednostką kodu możemy zakodować w sumie 232 znaki, czyli więcej niż 4M. ale pytanie brzmi, skąd dekoder UTF-16 będzie wiedział, że musi wziąć pod uwagę 2 jednostki kodu, aby dekodować znak?
UTF-8 rozwiązał ten problem, ustawiając początkowe bity pierwszej jednostki kodu i jednostki kodu kontynuacji na określone wartości bitowe, których dekoder UTF-8 może użyć do odliczenia liczby jednostek kodu, które może przyjąć znak.
możemy zrobić to samo z jednostką kodu UTF-16, ale wtedy musimy poświęcić kilka bitów w jednostce kodu dla tej funkcji. Możemy ustawić kilka początkowych bitów jednostki kodu na jakąś wartość, którą dekoder UTF-16 może zrozumieć.
również, aby dać autosynchronizację jednostek kodu, Jednostka kodu musi być w stanie stwierdzić, czy jest to początkowa Jednostka kodu, czy jednostka kodu kontynuacji, a nie znak tylko jednej jednostki kodu.
więc Unicode postanowił poświęcić początkowe 6 bitów jednostki kodu pozostawiając tylko 10 bitów do zakodowania punktu kodowego znaku na jednostkę kodu. Jeśli znak potrzebuje 2 jednostek kodu, 20 bitów pamięci (z 32 bitów lub 4 bajtów) zawiera faktyczną informację o punkcie kodowym znaku.
więc jakie są te początkowe bity i jak te bity robią wgniecenie w zestawie znaków UTF? Zastosujmy się do poniższego przykładu.
1101 10xx xxxx xxxx 1101 11xx xxxx xxxx
FIRST CODE UNIT---- SECOND CODE UNIT---
zgodnie ze standardem UTF-16 pierwsza jednostka kodu powinna zaczynać się od 110110₂, a druga jednostka kodu powinna zaczynać się od 110111₂. Pomoże to dekoderowi UTF-16 zrozumieć, który z nich jest pierwszą jednostką kodu, a który drugim. Dzięki temu UTF-16 samosynchronizuje się.
teraz mamy 10 bitów na jednostkę kodu do zabawy, w jakim zakresie możemy grać? W końcu, ile znaków można zakodować w dwóch jednostkach kodowania UTF-16?
nie martw się, porozmawiamy o znakach zakodowanych w jednej jednostce kodu.
Jeśli spojrzeć na powyższe szablony jednostek kodu, mamy zakres od 1101 1000 0000 0000₂ do 1101 1111 1111 1111₂. Jest to odpowiednik D800₁₆ do dfff₁₆.
first pierwsza jednostka kodu ma zakres od D800₁₆ do 6fff and, a druga jednostka kodu ma zakres od dc00₁₆ do DFFF.. Możemy uzyskać te wartości, włączając i wyłączając wszystkie bity punktów kodu.
ponieważ UTF-16 musi być samosynchronizowany, Punkty kodowe między D800₁₆ i dfff₁₆ nie mogą reprezentować znaku w UTF-16. Ponieważ wszystkie kodowania UTF mają ten sam zestaw znaków UTF, te Punkty kodowe są ograniczone przez UTF i nie są i nie będą przypisane do żadnych znaków⁰.
Punkty kodowe między D800₁₆ i dfff₁₆ nie reprezentują żadnych znaków, dlatego nazywane są zastępczymi punktami kodowymi lub razem nazywane są również parami zastępczymi⁰.
pierwszy surogatowy punkt kodowy (od pierwszej jednostki kodu) nazywany również wysokim surogatem, a drugi punkt kodowy (od drugiej jednostki kodu) nazywany również niskim surogatem. W sumie 2048 punktów kodowych, 1024 na surogatkę.
Pomyśl o tym!
więc wielkie pytanie, Czy możemy zakodować znak za pomocą jednej jednostki kodu UTF-16? Odpowiedź brzmi: tak. UTF-16 jest 16-bitowym schematem kodowania o zmiennej długości. Czy to oznacza, że możemy zakodować 21⁶ znaków za pomocą jednej jednostki kodu?
odpowiedź brzmi nie. W teorii, możemy zakodować 21⁶ postaci z punktu 0000₁₆ kod (0₁₀) w FFFF₁₆ (65535₁₀) ale kodowe punktu między D800₁₆ i DFFF₁₆ nie są jakieś znaki, a są one zarezerwowane.
dlatego bezpiecznie jest kodować znaki od 0000₁₆ do d7ff and I E000₁₆ do ffff leaving, pozostawiając 63 488 (65536-2048) znaków. Dotyczy to tylko znaków, które mogą być zakodowane w jednej jednostce kodowej UTF-16.
ponieważ mamy w sumie 20 bitów do zabawy, jeśli chodzi o znaki, niż mogą być zakodowane w 2 jednostkach kodu UTF-16, możemy zakodować 22⁰ więcej znaków, co jest 1,048,576 znaków.
w sumie możemy zakodować 1,048,576 + 63,488 co daje 1,112,064 znaków (ponad 1 milion znaków). Jest to limit zestawu znaków UTF. Ponieważ UTF-16 może kodować te wiele znaków, inne kodowania UTF nie mogą reprezentować znaków poza nimi.
UTF charset Code Points
ponieważ wiemy, że punkt kodu jest wartością dziesiętną przypisaną do znaku, my (Unicode) nie możemy przypisać nieprawidłowego punktu kodu do rzeczywistego znaku. Do tej pory nieprawidłowe Punkty kodowe są zastępczymi punktami kodowymi.
za pomocą jednej jednostki kodu UTF-16 możemy zakodować 63 488 znaków w zakresie od 0000₁₆ do d7ff and I E000₁₆ do Ffff₁₆. Ostatni punkt kodu to 65,535. Są to tzw. znaki BMP (wyjaśnione później).
dzięki dwóm jednostkom kodu UTF-16 możemy zakodować 1 048 576 znaków. Ponieważ nie możemy ponownie zacząć od wartości 0 (punkt kodowy), ponieważ pochodzą one po znakach BMP, musimy je zrównoważyć o 65,536. Znaki te nazywane są znakami uzupełniającymi (objaśnionymi później).
stąd pierwszy znak dodatkowy ma wartość punktu kodowego 65536₁₀, co jest równoważne 10000₁₆. Ponieważ możemy zakodować 1,048,576 znaków za pomocą dwóch jednostek kodu UTF-16, ostatnim punktem kodu jest 1114111₁₀, co jest równoważne 10FFFF₁₆.
więc Podzielmy rzeczy w prostej formie tabelarycznej.
+-----------+---------------------+--------------------+
| UTF-16 CU | Code Point | |
+-----------+---------------------+--------------------+
| 1 | 0000₁₆ - D7FF₁₆ | valid |
+-----------+---------------------+--------------------+
| 1 | D800₁₆ - DFFF₁₆ | invalid(surrogate) |
+-----------+---------------------+--------------------+
| 1 | E000₁₆ - FFFF₁₆ | valid |
+-----------+---------------------+--------------------+
| 2 | 10000₁₆ - 10FFFF₁₆ | valid |
+-----------+---------------------+--------------------+
| | 110000₁₆ - FFFFFF₁₆ | unassigned |
+-----------+---------------------+--------------------+
Dzięki tej wiedzy zobaczmy, jak możemy zakodować niektóre znaki w UTF-16. Wybierzmy prosty znak ASCII a (punkt kodowy: 41₁₆), znak z języka hindi (Indyjskiego) आ (wymawiany jako AA, punkt kodowy: 906₁₆) i Emotikon 😊 (zwany szczęśliwą twarzą, punkt kodowy: 1f60a₁₆).
jak widać z powyższej tabeli, zarówno A jak i आ mogą być zakodowane tylko w jednej jednostce kodowej UTF-16, ponieważ ich wartości są mniejsze niż FFFF₁₆.
Kiedy musimy zakodować znak tylko w jednej jednostce kodowej, musimy po prostu przekonwertować punkt kodowy znaku na 16-bitową liczbę binarną. Dla znaków a, 00000000 01000001 jest reprezentacją UTF-16.
podobnie, dla znaku आ, wystarczy skonwertować jego punkt kodowy 906₁₆ na 16-bitową liczbę binarną, która jest 00001001 00000110₂.
Zwykle reprezentujemy jednostki kodu znaku w liczbach szesnastkowych. Stąd dla znaku a, reprezentacja UTF-16 wynosi 0041₁₆ i podobnie dla znaku, reprezentacja UTF-16 आ wynosi 0906..
jak na postać😊, to jest trochę inaczej. Jego punkt kodowy to 1F60A₁₆. Jeśli spojrzymy na wspomnianą powyżej tabelę UTF-16, musi ona być zakodowana w 2 jednostkach kodu UTF-16. Więc jak zaczynamy?
najpierw musimy odjąć 10000₁₆ od punktu kodu. Powodem tego jest to, że każdy znak zakodowany w 2 jednostkach kodowych UTF-16 znajduje się po znakach BMP, których ostatnim punktem kodowym jest FFFF₁₆.
dlatego, aby uzyskać rzeczywistą wartość bitów używanych do kodowania (czyli 20 W 2 jednostkach kodu), musimy odjąć 10000₁₆ od punktu kodu i użyć ostatecznej liczby do wygenerowania tych 20 bitów.
💡 aby pomóc ci lepiej zrozumieć, pierwszy znak reprezentowany przez 2 jednostki kodu UTF-16 będzie miał wszystkie 20 bitów ustawionych na 0. Więc wartość bitów użytych do zakodowania punktu kodowego tego znaku wynosi 0. Ale nadal jego punkt kodowy wynosi 10000₁₆ zgodnie z zestawem znaków Unicode. Dzieje się tak, ponieważ wartość uzyskana przez te 20 bitów jest dodawana do 10000₁₆, aby wygenerować końcowy punkt kodu.
jak widać wcześniej, te 2 jednostki kodu wyglądają jak poniżej.
1101 10xx xxxx xxxx 1101 11xx xxxx xxxx
FIRST CODE UNIT---- SECOND CODE UNIT---
wystarczy wypełnić te 20 bitów (x
) wartością otrzymaną z punktu kodu znaku. Punktem kodowym znaku 😊jest 1F60A₁₆. Ale najpierw musimy odjąć od niego 10000₁₆. Otrzymujemy F60A₁₆.
teraz musimy przekonwertować F60A₁₆ na 20-bitową liczbę binarną i wypełnić 20 bitów w powyższym szablonie jednostki kodu. F60A₁₆ w układzie binarnym wynosi 0000111101 1000001010₂. Teraz możemy wypełnić te 20 bitów zastępczych.
poniżej znajdują się ostatnie jednostki kodu.
1101 1000 0011 1101 1101 1110 0000 1010
0xD83D 0xDE0A
szybki sposób na sprawdzenie, czy te jednostki kodu są poprawne i czy te pary zastępcze mogą reprezentować zakodowany znak, otwórz przeglądarkę DevTool i wprowadź console.log('\uD83D\uDE0A');
w konsoli.
Możesz również użyć tego narzędzia online do generowania punktów kodowych UTF-16.
płaszczyzny znaków Unicode
płaszczyzna jest ciągłą grupą 21⁶ lub 65,536 punktów kodowych. Ponieważ UTF-16 ograniczył Punkty kodowe do maksymalnie 10FFFF₁₆, mamy łącznie 17 płaszczyzn znaków w standardzie Unicode począwszy od 0 do 16.
ponieważ 21⁶ znaków może być zdefiniowanych przez pojedynczą jednostkę kodową UTF-16 (w tym zastępcze Punkty kodowe), tworzy pierwszą (0-tą) płaszczyznę. Ta płaszczyzna zawiera prawie wszystkie znaki w podstawowych językach na całym świecie. Dlatego ta płaszczyzna nazywana jest podstawową płaszczyzną wielojęzyczną lub BMP.
następnie mamy Punkty kodowe zdefiniowane przez dwie jednostki kodowe UTF-16. Zawierają one 22⁰ znaków, ponieważ mamy 20 bitów do zakodowania wartości punktu kodowego. Są one podzielone na 16 płaszczyzn (2 ♣ x 21♣). Są to tzw. płaszczyzny uzupełniające.
aby uzyskać więcej informacji na temat tych samolotów, przeczytaj ten dokument Wikipedii.
porównanie z UCS-2
UCS-2 jest 16-bitowym kodowaniem o stałej szerokości. Oznacza to, że tylko jedna 16-bitowa Jednostka kodu jest używana do reprezentowania punktu kodu. Teoretycznie UCS-2 może reprezentować 21⁶ różnych znaków, ale istnieje pewien zwrot.
💡 BTW, używamy terminu Jednostka kodu w tym kodowaniu o stałej szerokości, aby zrozumieć relację między UTF-16. W rzeczywistości nie ma czegoś takiego jak jednostka kodu w żadnym stałym z kodowaniem.
ponieważ UCS podąża za zestawem znaków Unicode, kodowanie znaków w UCS-2 jest identyczne z kodowaniem znaków w UTF-16, które są reprezentowane tylko w jednej jednostce kodu.
💡 ponieważ UCS podąża za zestawem znaków Unicode, nie może zakodować poprawnego znaku z punktami kodu zarezerwowanymi dla surogatów.
więc w skrócie, UCS-2 zawiera znaki podstawowej wielojęzycznej płaszczyzny. Z tego powodu niektóre starsze dokumenty i oprogramowanie używały kodowania UCS-2. Ale kodowanie UCS-2 stało się przestarzałe i preferowane jest UTF-16.
Endianness i BOM
jak już wcześniej omawialiśmy, dokumenty kodowane UTF na niskim poziomie zawierają sekwencję jednostek kodu. W przypadku UTF-8 jednostka kodu ma długość 8 bitów, natomiast w przypadku UTF-16 długość 16 bitów. Te jednostki kodu składają się na znaki.
dekoder UTF-8 lub UTF-16 odczytuje jednostki kodu sekwencyjnie, po jednej jednostce kodu na raz, aby wygenerować znaki.
każda jednostka kodu reprezentuje wartość liczbową, którą dekoder UTF-8 lub UTF-16 może przyjrzeć się i zdecydować, czy wystarczy reprezentować znak, czy podąża za innymi jednostkami kodu, które również powinny być brane pod uwagę.
jeśli chodzi o UTF-8, wszystko jest proste. Ponieważ każda jednostka kodu ma długość 8 bitów, konwersja tej 8-bitowej liczby binarnej na wartość liczbową jest szybka i łatwa. Nie jest to jednak przypadek UTF-16.
Jednostka kodowa UTF-16 jest 16-bitową (2-bajtową) liczbą binarną, która reprezentuje wartość punktu kodowego. Generowanie wartości liczbowej z wielu bajtów jest na ogół trudne i różne systemy zachowują się inaczej.
to zachowanie zależy od endianności systemu. Z naszej wcześniejszej dyskusji na temat endianności, istnieją dwa sposoby zapisu wartości jednostki kodu UTF-16. W formacie Big-endian lub Little-endian.
w formacie Big-endian, MSB jest zapisywany jako pierwszy, a LSB jako ostatni. Do tej pory zapisujemy Wartość jednostki kodu UTF-16 w formacie Big-endian. Aby zapisać wartość jednostki kodu UTF-16 W Little-endian, musimy zamienić bajty.
porozmawiajmy o postaci आ. Z wcześniejszego przykładu, może być reprezentowany tylko w jednej jednostce kodu UTF-16, a jego kodowanie w szesnastkowej reprezentacji wygląda jak 0906₁₆.
0906₁₆ jest 16-bitową liczbą z 09 jako MSB I 06 jako LSB. Stąd w architekturze Big-endyjskiej będzie on zapisany jako 09 06. Jednak w architekturze Little-endian będzie przechowywany jako 06 09.
dlatego naszym obowiązkiem staje się kodowanie znaków, biorąc pod uwagę endianność systemu, aby system mógł poprawnie odczytać dokument UTF-16.
ale jak możemy wcześniej stwierdzić, czy maszyna użytkownika jest zgodna z zakodowanym dokumentem, czy nie? A ponieważ endianność systemu może wpływać na sposób dekodowania dokumentu, Jak udostępnić go publicznie?
tu pojawia się BOM. Znak kolejności bajtów (BOM) to sekwencja bajtów dodawana na początku pliku tekstowego lub danych tekstowych.
Unicode zaleca znak z punktem kodowym FEFF₁₆ do działania jako BOM dla kodowania UTF-16 i UTF-32. Znak ten powinien znajdować się przed pierwszym znakiem dokumentu. Jednak ten znak nie będzie brany pod uwagę przez dekoder na wyjściu.
ten znak (U+FEFF) jest znakiem niezłamującej przestrzeni o zerowej szerokości (ZWNBSP) i jest niewidoczny. Stąd nawet jeśli dekoder nie rozpozna BOM, nie wytworzy żadnego widocznego wyjścia.
ten znak jest reprezentowany w pojedynczej jednostce kodu UTF-16, a w szesnastkowej reprezentacji wygląda jak FE (MSB) i FF (LSB).
dlatego, gdy znaki są zakodowane w formacie Big-endian, musimy dodać FEFF jako BOM na początku pliku, a gdy znaki są zakodowane w formacie Little-endian, musimy dodać fffe (reverse) jako BOM na początku pliku.
Unicode zaleca dodanie BOM do zakodowanego dokumentu UTF-16. Jeśli jednak brakuje BOM, przyjmuje się Format Big-endian.
IANA preferuje UTF-16 jako identyfikator oznaczający dokument zakodowany w UTF-16. Jednak UTF-16BE jest używany dla dokumentu zakodowanego w formacie Big-endian i UTF-16LE jest używany dla formatu Little-endian.
gdy używana jest nazwa UTF-16BE lub UTF-16LE, nie zaleca się dołączania BOM do pliku. Nawet w tym przypadku, jeśli BOM zostanie dodany, wtedy będzie on uważany za znak ZWNBSP i nie będzie ignorowany.
💡 nazwy UTF-16, UTF-16BE i UTF-16LE nie uwzględniają wielkości liter.
plusy i minusy
UTF-16 jest wydajny, ponieważ ma tylko 2 jednostki kodu, a ponieważ większość używanych znaków należy do zestawu BMP, mogą być reprezentowane tylko w jednej jednostce kodu. Jednak wiąże się to z wieloma problemami.
największą wadą UTF-16 jest to, że nie jest kompatybilny z ASCII. Ponieważ znaki ASCII są kodowane za pomocą jednej jednostki kodu (liczby 16-bitowej), nie mogą być poprawnie dekodowane przez dekoder ASCII.
UTF-16 zużywa niepotrzebne miejsce na znaki ASCII. W porównaniu do dokumentu zakodowanego w UTF-8, który zawiera tylko znaki ASCII, rozmiar tego samego dokumentu zakodowanego w UTF-16 jest dwa razy większy.
UTF-16 ma również wpływ na endianność systemu. Jeśli brak BOM i nie jest używany odpowiedni identyfikator kodowania (jak UTF-16LE), dokument zakodowany w UTF-16 może nie zostać poprawnie zdekodowany.
ze względu na charakter kodowania UTF-16 wprowadzono zastępcze punkty kodu, które nie mogą reprezentować prawidłowych znaków. Ponadto ograniczył zestaw znaków Unicode do 10FFFF₁₆ (ostatni punkt kodu).
pomimo tych faktów niektóre języki programowania, takie jak JavaScript, Java itp. systemy takie jak Windows preferują kodowanie UTF-16.