Sanyanggae LogoSanyanggae

💻 Web & Frontend

Was unterscheidet Unicode und UTF-8? Fehlerhaft dargestellter koreanischer Text, Bytes, Zeichenlänge und Normalisierung

Vergleichen Sie Codepunkte, UTF-8-Bytes und JavaScript-Zeichenlängen anhand koreanischer Zeichen und Emojis. Unterscheiden Sie falsche Dekodierung, Schriftprobleme, Normalisierung und die Erhaltung des Originals.

사냥개 (Sanyanggae)Veröffentlicht Aktualisiert 19 Min. Lesezeit

Dieser Artikel ist eine Übersetzung des koreanischen Originals. Koreanisches Original · Übersetzungsprüfung: Prüfung durch KI

Erklärdiagramm mit drei verbundenen Feldern: Zeichen und Codepunkte, UTF-8-Bytes sowie Darstellung und Normalisierung.
Eigenes Erklärdiagramm zur Unterscheidung von Unicode-Zeichennummern, gespeicherten UTF-8-Bytes sowie Darstellungs- und Vergleichsregeln. Kein tatsächlicher Fehlerbildschirm und kein Messergebnis.

1. Zeichennummern und gespeicherte Bytes sind verschieden

Stellen Sie sich vor, Sie exportieren ein koreanisches Dokument und öffnen es erneut: Der Titel erscheint plötzlich als seltsame Zeichenfolge. Nicht die Sprache der schreibenden Person hat sich verändert; möglicherweise stimmen die Regeln zum Lesen der gespeicherten Zahlen nicht überein. Unicode ordnet Zeichen und Symbolen verschiedener Schriftsysteme Nummern zu, sogenannte Codepunkte. UTF-8 ist eine Kodierung, die die entsprechenden Werte als Bytefolgen darstellt. Die koreanische Silbe ‘가’ hat beispielsweise den Codepunkt U+AC00. Die Angabe nach U+ ist hexadezimal; sie bezeichnet weder die Position in einer Datei noch eine Speicheradresse. Auch Schriftform und Bildschirmpixel werden dadurch nicht festgelegt. Derselbe Codepunkt kann je nach Schrift und Textlayout anders aussehen. Unicode-Unterstützung, Speicherung als UTF-8 und installierte koreanische Schriftarten hängen zusammen, müssen aber getrennt geprüft werden. Die UTF-Erklärung von Unicode bietet einen Ausgangspunkt, um Zeichen und Speicherformat auseinanderzuhalten.

In UTF-8 wird ‘가’ durch die drei hexadezimalen Bytes EA B0 80 dargestellt. Der lateinische Buchstabe A hat dagegen den Codepunkt U+0041 und wird als einzelnes Byte 41 gespeichert. Ein Byte umfasst acht Bits; schon innerhalb eines Dokuments können Zeichen unterschiedlich viele Bytes benötigen. Auch die Dateiendung .txt oder .csv verrät die tatsächliche Kodierung nicht zuverlässig. UTF-8 bildet gültige Unicode-Skalarwerte mit einem bis vier Bytes ab. Deshalb lässt sich die Dateigröße nicht exakt aus der sichtbaren Zeichenzahl multiplizieren. Das Beispiel einer koreanischen Silbe gilt auch nicht pauschal für alle koreanischen Zeichen oder Sprachen. Eine Kodierung überträgt Zeicheninformationen in Bytes; sie ist weder Kompression noch Verschlüsselung. Dateiformat-Zusätze, Zeilenumbrüche und Übertragungskompression können bei demselben Satz die Gesamtgröße verändern. Wer die im WHATWG Encoding Standard beschriebene Umwandlung zwischen Bytes und Zeichen von Dateigröße und Geheimhaltung unterscheidet, kann Fehler gezielter eingrenzen.

2. Bytes, Codeeinheiten und wahrgenommene Zeichen

Die Eigenschaft length einer JavaScript-Zeichenfolge zählt UTF-16-Codeeinheiten. Sie bezeichnet weder die Bytezahl einer UTF-8-Datei noch unbedingt die vom Menschen wahrgenommene Zeichenzahl. Das lächelnde Gesicht ‘😀’ besteht aus einem Codepunkt U+1F600, benötigt aber zwei UTF-16-Codeeinheiten: '😀'.length ergibt 2. Seine UTF-8-Darstellung F0 9F 98 80 umfasst vier Bytes. Die mit codePointAt(0) erhaltene Dezimalzahl 128512 ist dieselbe Nummer wie U+1F600, keine Zeichenzahl oder Dateigröße. ‘가’ besteht dagegen aus einem Codepunkt, einer UTF-16-Codeeinheit und drei UTF-8-Bytes. Unterschiedliche Ergebnisse für dieselbe Zeichenfolge erklären sich also durch verschiedene Maßeinheiten. MDN zur Zeichenfolgenlänge benennt ausdrücklich die Einheit von length. Daraus allein lässt sich auch nicht ableiten, wie viele reale Speicherbytes eine JavaScript-Engine intern verwendet.

Selbst das Zählen einzelner Codepunkte entspricht nicht immer der sichtbaren Zeichenzahl. Ein Grundzeichen kann kombinierende Zeichen tragen, und ein Emoji kann aus mehreren Codepunkten bestehen. Die Unicode-Regeln zur Textsegmentierung beschreiben die Grenzen erweiterter Graphemcluster, die solche Zusammenhänge berücksichtigen. Auch Array.from(text), das eine Zeichenfolge nach Codepunkten aufteilt, zählt solche Gruppen nicht automatisch als Einheit. Im Browser lässt sich nach Prüfung der Unterstützung Intl.Segmenter mit der granularity 'grapheme' erwägen. Graphemcluster entsprechen jedoch nicht garantiert jeweils einer einzigen gezeichneten Form, und die Erwartungen an die Textbearbeitung unterscheiden sich zwischen Sprachen. Ein Eingabefeld für eine Kurzbeschreibung sollte eine verständliche Zeichenzahl verwenden; Speicher- und Übertragungsgrenzen brauchen gegebenenfalls eigene Bytegrenzen. Wenn die Oberfläche „20 Zeichen“ verspricht, intern aber lediglich UTF-16-Codeeinheiten abschneidet, können Emojis oder kombinierte Zeichen mitten in ihrer Darstellung getrennt werden.

3. Bei beschädigtem Text Speicherung, Übertragung und Anzeige trennen

Unleserlicher Text kann entstehen, wenn beim Schreiben eine andere Kodierung verwendet wird als beim Lesen. Wird eine UTF-8-Datei mit einer anderen Dekodierung interpretiert, können falsche Zeichen erscheinen, obwohl die Datei selbst unverändert ist. Bei HTML sind das tatsächliche Speicherformat, die charset-Angabe im HTTP-Header Content-Type und die frühe meta-charset-Deklaration gemeinsam zu prüfen. Das HTML-Attribut lang='ko' bezeichnet die Sprache des Inhalts und hat eine andere Aufgabe als charset, das die Dekodierung betrifft. Die W3C-Anleitung zu Kodierungsangaben erklärt, dass nicht nur die Deklaration, sondern auch die tatsächlich gespeicherte Datei UTF-8 verwenden sollte und Server- und Dokumentangaben zusammenpassen müssen. Vorhandene Kodierungskennzeichen beeinflussen außerdem die Priorität der Erkennung; ein meta-Element überschreibt nicht immer alle anderen Angaben. Unterschiede zwischen direktem Öffnen und Abrufen über einen Server weisen auf die Übertragungsstufe hin.

Fehlen einer Schrift die benötigten Zeichenformen und erscheinen deshalb Kästchen, kann eine andere Ursache vorliegen als bei falscher Dekodierung. Sind die Zeichendaten richtig und fehlt lediglich eine passende Darstellung, hilft ein Wechsel der Speicherkodierung nicht. Umgekehrt stellt eine neue Schrift keine ursprünglichen Zeichen wieder her, wenn bereits andere Zeichen interpretiert wurden. Bewahren Sie für die Untersuchung eine Kopie der problematischen Datei auf und vergleichen Sie nacheinander die im Editor gewählte Kodierung, die Serverdeklaration, die vom Browser interpretierten Zeichen und die verwendete Schrift. Entscheiden Sie nicht allein nach dem Aussehen; prüfen Sie auch, welche Codepunkte beim Kopieren derselben Stelle vorliegen. Notieren Sie, ob das Problem nur in einem Browser auftritt oder andere Editoren dieselbe Datei ebenfalls falsch lesen. Wiederholtes Speichern, bis etwas plausibel aussieht, kann die ursprünglichen Bytes verlieren lassen. Stimmen alle Deklarationen einer Seite, während nur einzelne Beiträge betroffen sind, sollte auch die Verarbeitung vor dem Eintrag in die Datenbank untersucht werden.

4. Werte mit TextEncoder und TextDecoder nachvollziehen

Beginnen Sie ein einfaches Experiment mit einer selbst angelegten kurzen Zeichenfolge. TextEncoder wandelt eine JavaScript-Zeichenfolge in ein UTF-8-Bytearray um. Das Ergebnis von new TextEncoder().encode('가') lautet hexadezimal EA B0 80; die length des Arrays ist 3. Entwicklerwerkzeuge können dieselben Werte dezimal als 234, 176, 128 anzeigen. Das sind keine anderen Daten, sondern unterschiedliche Zahlendarstellungen. Der Vergleich mit der gewöhnlichen Zeichenfolgenlänge 1 macht die verschiedenen Einheiten deutlich. new TextDecoder('utf-8').decode(bytes) liefert aus diesem Array wieder ‘가’; bytes bezeichnet dabei das zuvor erzeugte Bytearray. Der Encoder erzeugt UTF-8 und ist kein universeller Konverter, mit dem sich jede ältere Kodierung als Ausgabe wählen lässt. Bei einer realen Datei müssen Sie von den ursprünglich gesicherten Bytes ausgehen. Das erneute UTF-8-Kodieren eines bereits falsch gelesenen Bildschirmtexts beweist nicht, welche Bytes die erste Datei enthielt.

Wenn Sie dem TextDecoder-Konstruktor 'utf-8' und { fatal: true } übergeben, kann ungültige Eingabe als Fehler behandelt werden, statt stillschweigend ersetzt zu werden. Das einzelne Byte FF ist beispielsweise keine gültige UTF-8-Eingabe, sodass seine Dekodierung in diesem Modus fehlschlägt. Im standardmäßigen Ersetzungsmodus kann an der ungültigen Stelle das Ersatzzeichen U+FFFD erscheinen. Fehlerfreiheit bedeutet jedoch nicht, dass damit die ursprüngliche Kodierung oder die inhaltliche Richtigkeit ermittelt wurde. Manche Bytefolgen sind in mehreren Kodierungen gültig; außerdem kann ein schon veränderter Text erneut als gültiges UTF-8 gespeichert worden sein. Der Test prüft, ob der ausgewählte Decoder die Eingabe annehmen kann. Er überprüft nicht automatisch die Herkunft des Dokuments, die Übereinstimmung mit dem Original oder eine mögliche zwischenzeitliche Manipulation.

5. Normalisierung vereinheitlicht gleichwertige Zeichenfolgen

Auch zwei korrekt dekodierte Zeichenfolgen können intern unterschiedliche Codepunktfolgen haben. ‘가’ lässt sich als einzelnes U+AC00 oder als Folge des kombinierenden Anfangskonsonanten U+1100 und Vokals U+1161 darstellen. Die letzten beiden Werte sind nicht einfach die alltäglichen Kompatibilitätszeichen ‘ㄱ’ und ‘ㅏ’; betrachten Sie daher auch die Nummern. Beide Darstellungen erscheinen gewöhnlich als dieselbe Silbe, müssen aber bei einem direkten JavaScript-Zeichenfolgenvergleich nicht gleich sein. Werden beide Seiten nach den Unicode-Normalisierungsregeln in NFC überführt, können diese kanonisch gleichwertigen Formen identisch verglichen werden. NFD verwendet die kanonische Zerlegung. In diesem Beispiel benötigt die NFC-Form in UTF-8 drei Bytes, die beiden Jamo der NFD-Form dagegen sechs: Gleiches Aussehen garantiert keine gleiche Speichergröße. Dieser Unterschied kann erklären, warum Suchbegriff und gespeicherter Name trotz gleicher Ansicht nicht übereinstimmen. Er ist von einer falschen Dekodierung zu unterscheiden.

Auch die Wahl der Normalisierungsform hängt vom Zweck ab. NFC behandelt kanonische Gleichwertigkeit; NFKC schließt dagegen Kompatibilitätszerlegungen ein und kann beispielsweise vollbreite oder eingekreiste Ziffern umfassender verändern. Für Suchvergleichswerte kann das nützlich sein, während Unterschiede in mathematischer Schreibweise oder Originalmaterial Bedeutung tragen können. Es ist deshalb hilfreich, das Original aufzubewahren und gezielt festzulegen, welche Form auf eine Vergleichskopie angewendet wird. Normalisierung ersetzt weder Groß-/Kleinschreibungsvergleiche noch sprachabhängiges Sortieren oder das Finden gleich ausgesprochener Wörter. Eine pauschale Umwandlung vorhandener Dateinamen oder Kontokennungen kann unterschiedliche Einträge auf denselben Vergleichswert abbilden; prüfen Sie vorher Namensregeln und Kollisionsbehandlung des Dienstes. Alle ähnlich aussehenden Zeichen unter demselben gespeicherten Wert zusammenzufassen, ist kein allgemeines Ziel der Normalisierung.

6. Originale erhalten und Umwandlungsgrenzen dokumentieren

Bevor Sie eine ursprüngliche Datei überschreiben, sichern Sie ihre Bytes und dokumentieren Sie, wie Sie sie erhalten haben. Prüfen Sie auf einer Kopie die vermutete Dekodierung und anschließend, ob korrekt erscheinender Inhalt nach erneutem Speichern erhalten bleibt. Das am Dateianfang mögliche UTF-8-Kennzeichen aus der W3C-Erklärung zum BOM lautet EF BB BF und ist nicht obligatorisch. Bei UTF-8 muss keine Byte-Reihenfolge ausgewählt werden; das Kennzeichen hat daher nicht dieselbe Aufgabe wie die Reihenfolgenwahl bei UTF-16. Das Hinzufügen oder Entfernen eines BOM unterscheidet sich auch von einer Umkodierung der eigentlichen Textbytes. Wurden nicht lesbare Werte bereits durch Ersatzzeichen oder Fragezeichen ersetzt und gespeichert, können mehrere ursprüngliche Werte zusammengefallen sein; eine automatische Wiederherstellung ist dann nicht garantiert. Möglichkeiten zum Rückgängigmachen lassen sich nur bei bekannter ursprünglicher Kodierung und Verarbeitungsgeschichte beurteilen. Normalisierung stellt verlorene Bytes nicht wieder her.

Prüfen Sie mehrsprachige Funktionen nicht nur mit einer englischen Zeile. Vergleichen Sie Eingabe, Speicherung, erneuten Abruf und Export anhand kurzer Beispielsätze mit koreanischen Silben, lateinischen Buchstaben mit kombinierenden Zeichen, einem Emoji außerhalb der Basisebene und von rechts nach links geschriebenen Schriften wie Arabisch oder Hebräisch. Kontrollieren Sie in jeder Stufe Aussehen, Codepunkte, UTF-8-Bytezahl und die produktseitig festgelegte Zählweise. Die Leserichtung wird nicht durch eine andere Zeichenkodierung statt UTF-8 gelöst; Sprach- und Richtungseinstellungen der Oberfläche sind gesondert zu prüfen. Bewahren Sie auch bei Änderungen an Eingabegrenzen oder Suchvergleichen vorhandene Originaltexte auf und erklären Sie die geänderten Kriterien. Browseranzeige und serverseitige Zulassungsgrenze sollten dieselbe Einheit verwenden; sind unterschiedliche Grenzen nötig, müssen beide bezeichnet werden. Die Zahlen und Codebeispiele dieses Artikels zeigen Umwandlungen kurzer Beispielzeichenfolgen und sind keine Messung eines bestimmten Dienstausfalls. So lässt sich zwischen Zeichennummer, gespeicherten Bytes, Zeichenfolgeneinheiten, Darstellung und Vergleich nachvollziehen, an welcher Grenze sich etwas geändert hat.

Quellen und weiterführende Literatur

Dieser Artikel erläutert Informationen aus öffentlichen Quellen. Die Nennung einer Einrichtung bedeutet weder deren Prüfung noch deren Zustimmung.

Herausgeber und Kontakt

Weitere Artikel zum Thema