Lektion 1: Bits und Bytes
Ein Computer kennt nur 0 und 1. Ein Bit ist ein Schalter; 8 Bit ergeben ein Byte und damit 28=256 verschiedene Werte. Über die Stellenwerte wird aus Binär eine Dezimalzahl: 01000001 = 64 + 1 = 65. Jeder Text ist am Ende eine Bytefolge, und eine „Zeichenkodierung“ ist eine Tabelle, die festlegt, welche Zahl für welches Zeichen steht – verschiedene Tabellen sind verschiedene Kodierungen.
Lektionen 2–3: ASCII
ASCII definierte 1963 mit 7 Bit 128 Zeichen: 0–31 sind Steuerzeichen wie Zeilenumbruch, Tabulator und Glocke, 48–57 sind die Ziffern 0–9, 65–90 die Großbuchstaben, 97–122 die Kleinbuchstaben. Die Tabelle ist nicht beliebig: Groß- und Kleinbuchstaben unterscheiden sich um 32 (das 6. Bit), deshalb macht c | 0x20 aus einem Großbuchstaben einen Kleinbuchstaben; eine Ziffer minus 48 ergibt ihren Zahlenwert. Hexadezimal ist die übliche Schreibweise zum Lesen von Bytes, zwei Ziffern für ein Byte: 0x48 0x69 = Hi. ASCII nutzte nur 7 Bit und ließ das 8. frei – später füllten es verschiedene Länder auf eigene, untereinander inkompatible Weise (Latin-1, GBK, Shift_JIS und so weiter), und genau das ist die historische Wurzel des Mojibake.
Lektion 4: Unicode-Codepoints
Unicode vergibt weltweit einheitliche Nummern, genannt Codepoint, geschrieben als U+ plus Hexadezimalzahl: A ist U+0041, 中 ist U+4E2D, 😀 ist U+1F600. Der Raum reicht von U+0000 bis U+10FFFF und ist in 17 „Ebenen“ geteilt; die gebräuchlichen Zeichen liegen fast alle in der nullten, der Basic Multilingual Plane (U+0000–U+FFFF), Emojis und seltene CJK-Zeichen in den Ergänzungsebenen. Entscheidend: Ein Codepoint ist nur eine Nummer; wie daraus Bytes werden, bestimmen „Kodierungsformen“ wie UTF-8, UTF-16 oder UTF-32. „Unicode“ ist keine Kodierung, utf-8 ist eine.
Lektionen 5–8: Die variablen Templates von UTF-8
UTF-8 (RFC 3629) stellt einen Codepoint mit 1–4 Bytes dar, und die Regeln stehen vollständig im Präfix des ersten Bytes:
| Codepoint-Bereich |
Bytes |
Template |
Nutzbits |
| U+0000–U+007F |
1 |
0xxxxxxx |
7 |
| U+0080–U+07FF |
2 |
110xxxxx 10xxxxxx |
11 |
| U+0800–U+FFFF |
3 |
1110xxxx 10xxxxxx 10xxxxxx |
16 |
| U+10000–U+10FFFF |
4 |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
21 |
Man füllt die Binärdarstellung des Codepoints links mit Nullen auf die Zahl der Nutzbits auf und trägt sie der Reihe nach in die x ein. é = U+00E9 = 11101001, auf 11 Bit aufgefüllt 00011 101001, in das Zwei-Byte-Template eingesetzt 11000011 10101001 = C3 A9.
Dieses Design hat drei Vorteile: ASCII-Text bleibt nach der UTF-8-Kodierung Byte für Byte unverändert (vollständig abwärtskompatibel); jedes Folgebyte beginnt mit 10, sodass man aus jeder Position im Bytestrom den Anfang des nächsten Zeichens findet (Selbstsynchronisation – ein verlorenes Byte verschiebt nicht den ganzen Rest); und Kodierungen unterschiedlicher Länge überlappen sich nicht, das Nullbyte 00 steht immer nur für U+0000 (sicher für C-Strings). Der Preis: Ein chinesisches Zeichen braucht 3 Bytes, 50 % mehr als die 2 Bytes bei GBK; englischer Text, Satzzeichen und Code sind nicht betroffen, deshalb nutzen über 98 % aller Webseiten UTF-8.
Lektion 7 und 8 lassen dich selbst ein 3-Byte-Zeichen und ein 4-Byte-Zeichen eingeben. Letzteres deckt eine bis heute häufige Falle auf: Der MySQL-Zeichensatz utf8 unterstützt in Wahrheit nur 3 Bytes und wirft bei Emojis einen Fehler oder schneidet sie ab – nötig ist utf8mb4.
Lektion 9: UTF-16 und .length
JavaScript, Java, C# und die Windows-API nutzen intern UTF-16: Jede Codeeinheit (code unit) ist 16 Bit. Zeichen der Basic Multilingual Plane belegen eine Codeeinheit; Codepoints der Ergänzungsebenen werden auf zwei Codeeinheiten aufgeteilt, ein Surrogatpaar (surrogate pair) aus den Bereichen D800–DBFF und DC00–DFFF. Deshalb gilt "😀".length === 2, "😀".split("") zerlegt in zwei bedeutungslose Hälften, und str[0] liefert das hohe Surrogat. Für die echte Zeichenzahl nutzt du [...str].length (Iteration über Codepoints); für die vom Nutzer wahrgenommene „eine Einheit“ (inklusive kombinierter Emojis und Diakritika) Intl.Segmenter.
Lektionen 10–11: Base64
Base64 (RFC 4648) ist keine Verschlüsselung, sondern ein Transportverfahren, das beliebige Bytes in 64 druckbare ASCII-Zeichen (A–Z a–z 0–9 + /) verwandelt, um Binärdaten dort unterzubringen, wo nur Text erlaubt ist: E-Mail-Anhänge, JSON-Felder, data:-URLs, JWT. Das Verfahren: je 3 Bytes = 24 Bit, aufgeteilt in 4 Sechsergruppen (26=64), jede Gruppe über die Tabelle in ein Zeichen übersetzt: Man = 4D 61 6E = 01001101 01100001 01101110 → 010011 010110 000101 101110 → T W F u. Das Volumen wächst dadurch auf das 4/3-Fache.
Ist die Bytezahl kein Vielfaches von 3, bleibt die letzte Gruppe unvollständig: Fehlt 1 Byte, kommt ein = dazu, fehlen 2 Bytes, zwei =, damit die Ausgabelänge immer ein Vielfaches von 4 ist. A ist ein Byte mit 8 Bit, auf 12 Bit mit Nullen aufgefüllt, in 010000 01(0000) zerlegt → QQ, plus ==. Die URL-sichere Variante ersetzt + / durch - _ (damit in URLs nichts escapt werden muss) und lässt das = oft weg; beim Dekodieren wird es nach Länge ergänzt.
Lektion 12: URL-Prozentkodierung
In URLs darf nur ein kleiner Teil der ASCII-Zeichen unverändert vorkommen (RFC 3986 §2). Leerzeichen, &, =, ?, / und andere reservierte Zeichen mit syntaktischer Bedeutung sowie alle Nicht-ASCII-Zeichen werden als % plus Hexadezimalwert des Bytes geschrieben. URL-Kodierung ist also erst UTF-8, dann pro Byte ein %: 中 → E4 B8 AD → %E4%B8%AD, aus einem chinesischen Zeichen werden 9 Zeichen. In JavaScript kodiert encodeURIComponent am gründlichsten (auch / ? & =), passend für Parameterwerte; encodeURI lässt Strukturzeichen stehen und eignet sich für die ganze Adresse. Formulardaten als application/x-www-form-urlencoded schreiben Leerzeichen zusätzlich als + – eine historische Konvention der HTML-Formulare und nicht Teil von RFC 3986.
Lektionen 13–14: Woher Mojibake kommt und wie man ihn rettet
Mojibake ist keine Datenbeschädigung, sondern beim Schreiben Kodierung A, beim Lesen Kodierung B. Das UTF-8-中 ist E4 B8 AD:
- Als Windows-1252 / Latin-1 gelesen (ein Byte, ein Zeichen): Die drei Bytes werden zu
ä¸ – drei westeuropäische Buchstaben. Eine Kette von Buchstaben mit Diakritika ist das Kennzeichen für „UTF-8 wurde als westeuropäische Kodierung gelesen“.
- Als GBK gelesen (zwei Bytes, ein Schriftzeichen):
E4 B8 ist ein Schriftzeichen 涓, vom Rest bleibt AD als halbes Zeichen – „seltsame Schriftzeichen plus ein halbes Zeichen“ ist das Kennzeichen für „UTF-8 wurde als GBK gelesen“, 浣犲ソ und 涓枃 gehören dazu.
- Umgekehrt: Wird GBK-Text als UTF-8 gelesen, erscheinen bei ungültigen Bytefolgen massenhaft Ersatzzeichen
� (U+FFFD).
Solange die Originalbytes erhalten sind, genügt das erneute Lesen mit der richtigen Kodierung, und der Text ist wiederhergestellt. Erste Regel bei der Rettung ist deshalb: Originaldatei behalten, nicht wiederholt umkodieren – jede Konvertierung kann ungültige Bytes durch ? oder � ersetzen, und erst das ist echter Informationsverlust. Lektion 14 verlangt die Umkehrung: Mojibake 浣犲ソ über GBK zurück in die Bytes E4 BD A0 E5 A5 BD und über UTF-8 gelesen als 你好. Modern geht man so vor, dass man in HTTP-Headern, HTML-<meta charset>, Datenbankverbindungen und beim Speichern von Dateien konsequent UTF-8 deklariert und Fehlpaarungen gar nicht erst zulässt.
BOM und Byte-Reihenfolge
UTF-16 kennt eine große und eine kleine Byte-Reihenfolge; am Dateianfang steht oft ein Byte-Order-Mark BOM (FE FF oder FF FE). UTF-8 hat kein Byte-Reihenfolge-Problem, doch der Windows-Editor Notepad schrieb früher standardmäßig ein UTF-8-BOM EF BB BF, was in Shell-Skripten die erste Zeile scheitern ließ und vor die erste CSV-Spalte unsichtbare Zeichen setzte. Siehst du in der UTF-8-Zerlegung des Kurses diese drei Bytes am Anfang, kennst du die Quelle.