blog · git · desktop · images · contact & privacy · gopher
2016-10-05
Disclaimer: Ich habe über dieses Thema in einem anderen Blog zuerst gelesen. Ich kann das Posting aber einfach nicht mehr finden. Solltest du, sozusagen der originale Autor, das hier lesen, dann schreib’ mich einfach an und ich füge einen Link zu deinem Posting hinzu.
Okay, wir haben jetzt 2016 und ich habe mir endlich mal die Zeit genommen, einen einfachen Encoder und Decoder für UTF-8 zu schreiben. Ich finde, das ist eine wichtige Sache, die jeder mal gemacht haben sollte. Es fördert das Verständnis.
Wenn man anfängt, sich mit der Thematik zu beschäftigen, stößt man schnell auf Notationen wie diese hier in der Wikipedia:
Hexadezimale Schreibweise. Natürlich, warum auch nicht, verwendet man ja oft, wenn man mit „rohen“ Daten zu tun hat. Es ist gängige Praxis. Leider verkompliziert hex viele Dinge, wenn es um UTF-8 geht. Schauen wir uns dazu ganz kurz an, wie UTF-8 funktioniert.
In UTF-8 gibt es „Start-Bytes“ und „Folge-Bytes“. Ein Start-Byte signalisiert, dass an dieser Stelle eine Multibytesequenz beginnt. Unmittelbar danach folgen einige Folge-Bytes. Nimmt man eine solche Folge, dann kann man sie dekodieren und erhält einen Unicode-Codepoint.
Eine Multibytesequenz kann irgendwie so aussehen:
0xxxxxxx
: Das höchste Bit ist nicht gesetzt, wir sind also im
ASCII-Bereich. Es gibt nichts zu dekodieren und keine Folge-Bytes
folgen.110xxxxx 10xxxxxx
: Ein Start-Byte und ein Folge-Byte.1110xxxx 10xxxxxx 10xxxxxx
: Ein Start-Byte und zwei Folge-Bytes.11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
: Drei Folge-Bytes.Das war’s eigentlich auch schon. In der Theorie kann UTF-8 zwar Sequenzen aus bis zu 8 Bytes enthalten, RFC 3629 hat die erlaubte Anzahl aber auf 4 Bytes reduziert.
Die x-e oben können dann beliebige Bits enthalten. Diese Bits geben den Codepoint in Unicode an. (Manche Codepoints sind „verboten“, aber das ignorieren wir mal.) Im Wesentlichen funktioniert das so, dass der Codepoint irgendeine abstrakte Nummer ist und die Bits dieser Nummer werden dann auf Start- und Folge-Bytes verteilt. Ist der Codepoint also eine niedrige Zahl, dann braucht man nur wenige Bytes (oder nur eines) – ist er eine große Zahl, benötigt man entsprechend längere Sequenzen.
Okay, jetzt genau hinschauen. Alle Folge-Bytes enthalten immer genau 6
Bits an Information. In oktaler Schreibweise sind das genau zwei
Ziffern. Die führenden Bits 10...
werden immer zu einer führenden 2
in oktal.
Etwas ähnliches kann man auch mit den Start-Bytes machen. Fangen diese
mit 110...
oder 11110...
an, dann fängt die oktale Zahl mit 3
oder
36
an und die restlichen oktalen Ziffern sind direkt Teil des
Codepoints. Fängt das Start-Byte mit 1110...
an, dann wird es etwas
komplizierter, aber dazu unten mehr.
Was bringt das jetzt? Naja, wenn man UTF-8-kodierte Daten in oktaler Schreibweise liest, dann kann man direkt den Unicode-Codepoint ablesen, ohne etwas dekodieren zu müssen. Ein paar Beispiele:
Der Umlaut „ö“, oktaler Unicode-Codepoint ist 366 (hex <U+00F6>
):
$ printf ö | od -t o1
0000000 303 266
^^ ^^
Euro-Zeichen „€“, oktaler Unicode-Codepoint ist 20254 (hex
<U+20AC>
):
$ printf € | od -t o1
0000000 342 202 254
^ ^^ ^^
Das asiatische Zeichen „灀“, dessen Bedeutung ich leider nicht
kenne, Unicode-Codepoint in oktal ist 70100 (hex <U+7040>
):
$ printf 灀 | od -t o1
0000000 347 201 200
^ ^^ ^^
„Daumen hoch“, oktaler Unicode-Codepoint ist 372115 (hex
<U+1F44D>
):
$ printf 👍 | od -t o1
0000000 360 237 221 215
^ ^^ ^^ ^^
Eine kleine Falle gibt es aber. Wenn man es mit 3-Byte-Sequenzen zu tun
hat und der Codepoint oberhalb von hex <U+7FFF>
liegt, dann muss man
auch die zweite oktale Ziffer beachten – gewissermaßen. Beispiel:
„キ“ ist der oktale Codepoint 177567 (hex <U+FF77>
) und dann sieht
das so aus:
$ printf キ | od -t o1
0000000 357 275 267
!^ ^^ ^^
Die „57“ muss man also als „17“ lesen.
Insgesamt ergeben sich folgende einfache Regeln:
Diese paar einfachen Regeln sollten ausreichen, um im folgenden oktalen Dump die UTF-8-Sequenzen erkennen und sogar „dekodieren“ zu können:
$ od -t o1 <data
0000000 124 150 141 164 342 200 231 163 040 141 156 040 145 170 145 155
0000020 160 154 141 162 171 040 144 165 155 160 056 040 342 200 234 110
0000040 141 154 154 303 266 143 150 145 156 054 342 200 235 040 150 145
0000060 040 163 141 151 144 056 012
Wäre die Konvention, um Unicode-Codepoints zu notieren ein oktales
<U+372115>
statt einem hexadezimalen <U+1F44D>
, dann wäre das schon
sehr praktisch. Leider ist das nicht der Fall. Natürlich wusste damals
noch niemand, dass UTF-8 mal das Encoding für Unicode werden würde,
also kann man den Leuten da keinen Vorwurf machen.
Das Wissen um dieses Verhalten ist trotzdem hilfreich. Wenn man oktale Dumps liest, kann man sehr einfach die Ziffern der Codepoints erfassen, muss sie dann nur nach hex umrechnen und fertig. Und selbst wenn man überhaupt nicht daran interessiert ist, etwas zu „dekodieren“, kann es hilfreich sein, das alles zu wissen: Bytes, die mit 3 oder 2 anfangen sind sehr leicht vom Rest unterscheidbar – und schon weiß man, dass das eine UTF-8-Sequenz ist. In hex ist das viel schwieriger.