Woher kommen Mojibakes? Grundlagen der Kodierung


In diesem Artikel werden die grundlegenden Konzepte der Zeichenkodierung erläutert. Anschließend werden die technischen Details von Kodierungssystemen vertieft.


Wenn Sie nur Grundkenntnisse in der Zeichenkodierung haben und das Wesentliche, die Unterschiede zwischen den Kodierungssystemen, warum wir manchmal Unsinntext erhalten, und die Prinzipien hinter verschiedenen Kodierungssystemarchitekturen besser verstehen möchten, lesen Sie weiter.


Um die Zeichenkodierung im Detail zu verstehen, ist ein umfangreiches Lesen und ein guter Zeitaufwand erforderlich. Ich habe versucht, Ihnen einen Teil dieser Mühe zu ersparen, indem ich alles an einem Ort zusammengefasst habe und dabei einen meiner Meinung nach recht gründlichen Hintergrund für das Thema geboten habe.


Ich werde über die Funktionsweise von Einzelbyte-Codierungen (ASCII, Windows-1251 usw.), die Entstehungsgeschichte von Unicode, die Unicode-basierten Codierungen UTF-8, UTF-16 und deren Unterschiede nachdenken spezifische Merkmale, Kompatibilität und deren Fehlen zwischen verschiedenen Codierungen, Zeichencodierungsprinzipien und eine praktische Anleitung, wie Zeichen codiert und decodiert werden.


Auch wenn die Zeichenkodierung kein aktuelles Thema ist, ist es hilfreich zu verstehen, wie es jetzt funktioniert und wie es in der Vergangenheit funktioniert hat, ohne viel Zeit zu investieren.


Geschichte von Unicode


Ich denke, es ist das Beste, unsere Geschichte von einer Zeit an zu beginnen, in der Computer noch längst nicht so weit fortgeschritten und alltäglich waren wie heute. Entwickler und Ingenieure, die zu dieser Zeit versuchten, Standards zu entwickeln, ahnten nicht, dass Computer und das Internet genauso populär und allgegenwärtig sein würden wie sie. Als das passierte, brauchte die Welt Zeichenkodierungen.


Aber wie kann ein Computer Zeichen oder Buchstaben speichern, wenn er nur Einsen und Nullen versteht? Aus diesem Bedürfnis ging die erste 1-Byte-ASCII-Codierung hervor, die zwar nicht unbedingt die erste Codierung ist, jedoch am weitesten verbreitet ist und Maßstäbe setzt. Es ist also ein guter Standard.


Aber was ist ASCII? Der ASCII-Code besteht aus 8 Bits. Eine einfache Arithmetik zeigt, dass dieser Zeichensatz 256 Symbole enthält (acht Bits, Nullen und Einsen 2⁸ = 256).


Die ersten 7-Bit-128-Symbole (2⁷ = 128) im Satz wurden für lateinische Buchstaben, Steuerzeichen (wie harte Zeilenumbrüche, Tabulatoren usw.) und grammatikalische Symbole verwendet. Die anderen Teile waren für Landessprachen. Auf diese Weise sind die ersten 128 Zeichen immer gleich. Wenn Sie Ihre Muttersprache codieren möchten, helfen Sie sich selbst bei den verbleibenden Symbolen.


Dies führte zu einer Vielzahl nationaler Kodierungen. Am Ende sieht die Situation so aus: Angenommen, Sie befinden sich in Russland und erstellen eine Textdatei, die standardmäßig Windows-1251 (die in Windows verwendete russische Codierung) verwendet. Und Sie senden Ihr Dokument an jemanden außerhalb Russlands, beispielsweise in den USA. Selbst wenn der Empfänger Russisch kann, hat er Pech, wenn er das Dokument auf seinem Computer öffnet (mit einer Textverarbeitungssoftware, die ASCII als Standardcode verwendet), da statt russischer Buchstaben bizarre verstümmelte Zeichen (Mojibake) angezeigt werden . Genauer gesagt, alle englischen Buchstaben werden einwandfrei angezeigt, da die ersten 128 Symbole in Windows-1251 und ASCII identisch sind. Wenn jedoch russischer Text vorhanden ist, verwendet die Textverarbeitungssoftware des Empfängers die falsche Codierung, es sei denn, der Benutzer hat das richtige Zeichen manuell eingegeben Kodierung.


Das Problem mit den nationalen Zeichencode-Standards liegt auf der Hand. Und schließlich begannen sich diese nationalen Codes zu vermehren, das Internet begann zu explodieren, und jeder wollte in seiner Landessprache schreiben, ohne diese nicht entzifferbaren Mojibakes zu produzieren.


Zu diesem Zeitpunkt gab es zwei Möglichkeiten: Verwenden Sie eine Kodierung für jedes Land oder erstellen Sie eine universelle Zeichentabelle, um alle Zeichen auf dem Planeten darzustellen.


Eine kurze Einführung in ASCII


Es mag übermäßig elementar erscheinen, aber wenn wir gründlich sein wollen, müssen wir alle Grundlagen abdecken.



Die ASCII-Tabelle enthält drei Spaltengruppen:


  • der Dezimalwert des Zeichens
  • der hexadezimale Wert des Zeichens
  • die Glyphe für den Charakter selbst

Angenommen, wir möchten das Wort "ok" in ASCII codieren. Der Buchstabe "o" hat einen Dezimalwert von 111 und 6F in hexadezimaler Schreibweise. In der Binärdatei wäre das - 01101111. Der Buchstabe "k" ist die Position 107 in Dezimal und 6B in Hexadezimal oder - 01101011 in Binärdatei. Das Wort "OK" in ASCII würde also wie folgt aussehen: 01101111 01101011. Der Dekodierungsprozess wäre umgekehrt. Wir beginnen mit 8 Bits, übersetzen sie in eine Dezimalcodierung und enden mit der Zeichennummer und durchsuchen die Tabelle nach dem entsprechenden Symbol.


Unicode


Aus dem obigen sollte ziemlich offensichtlich sein, warum eine einzige gemeinsame Zeichentabelle benötigt wurde. Aber wie würde es aussehen? Die Antwort ist Unicode, eigentlich keine Kodierung, sondern ein Zeichensatz. Es besteht aus 1.114.112 Positionen oder Codepunkten, von denen die meisten noch leer sind. Daher ist es unwahrscheinlich, dass das Set erweitert werden muss.


Der Unicode-Standard besteht aus 17 Ebenen mit jeweils 65.536 Codepunkten. Jede Ebene enthält eine Gruppe von Symbolen. Die Ebene Null ist die mehrsprachige Grundebene, in der wir die in allen modernen Alphabeten am häufigsten verwendeten Zeichen finden. Die zweite Ebene enthält Zeichen aus toten Sprachen. Es gibt sogar zwei Flugzeuge, die für den privaten Gebrauch reserviert sind. Die meisten Flugzeuge sind noch leer.


Unicode hat Codepunkte für 0 bis 10FFFF (hexadezimal).


Zeichen werden im Hexadezimalformat mit einem vorangestellten „U +“ codiert. So enthält beispielsweise die erste Grundebene die Zeichen U + 0000 bis U + FFFF (0 bis 65.535), und der Block 17 enthält U + 100000 bis U + 10FFFF (1.048.576 bis 1.114.111).


Anstelle einer Menagerie mit zahlreichen Kodierungen haben wir jetzt eine umfassende Tabelle, die alle Symbole und Zeichen kodiert, die wir jemals benötigen könnten. Aber es ist nicht ohne Fehler. Während jedes Zeichen zuvor mit einem Byte codiert wurde, kann es jetzt mit einer anderen Anzahl von Bytes codiert werden. Früher brauchten Sie nur ein Byte, um alle Buchstaben des englischen Alphabets zu kodieren. Der lateinische Buchstabe „o“ in Unicode lautet beispielsweise U + 006F. Mit anderen Worten, dieselbe Zahl wie in ASCII - 6F in hexadezimaler Schreibweise und 111 in binärer Schreibweise. Um das Symbol „U + 103D5“ (die persische Zahl „100“) zu codieren, benötigen wir 103D5 in hexadezimaler Schreibweise und 66.517 in dezimaler Schreibweise. Jetzt benötigen wir drei Bytes.


Dieser Komplexität muss mit Unicode-Codierungen wie UTF-8 und UTF-16 begegnet werden. Und weiter schauen wir sie uns an.


Utf-8


UTF-8 ist eine Unicode-Codierung des Codierungssystems mit variabler Breite, mit der jedes Unicode-Symbol angezeigt werden kann.


Was meinen wir, wenn wir von variabler Breite sprechen? Zunächst müssen wir verstehen, dass die strukturelle (atomare) Einheit bei der Codierung ein Byte ist. Codierung mit variabler Breite bedeutet, dass ein Zeichen mit einer unterschiedlichen Anzahl von Einheiten oder Bytes codiert werden kann. Beispielsweise werden lateinische Buchstaben mit einem Byte und kyrillische Buchstaben mit zwei Bytes codiert.


Bevor wir weitermachen, noch etwas zur Kompatibilität zwischen ASCII und UTF.


Die Tatsache, dass lateinische Buchstaben und Schlüssel Steuerzeichen wie Zeilenumbrüche, Tabulatoren usw. Ein Byte enthalten macht die UTF-Codierung mit ASCII kompatibel. Mit anderen Worten, lateinische Schrift- und Steuerzeichen befinden sich in ASCII und UTF an genau denselben Codepunkten und werden mit jeweils einem Byte codiert und sind daher abwärtskompatibel.


Verwenden wir den Buchstaben "o" aus unserem früheren ASCII-Beispiel. Denken Sie daran, dass seine Position in der ASCII-Tabelle 111 oder 01101111 in binärer Form ist. In der Unicode-Tabelle ist es U + 006F oder 01101111. Und jetzt, da UTF ein Codierungssystem mit variabler Breite ist, würde "o" ein Byte sein. Mit anderen Worten, "o" würde in beiden Fällen gleich dargestellt. Dasselbe gilt für die Zeichen 0 bis 128. Wenn Ihr Dokument also englische Buchstaben enthält, bemerken Sie keinen Unterschied, wenn Sie es mit UTF-8, UTF-16 oder ASCII öffnen, und bemerken einen Unterschied nur, wenn Sie mit der Arbeit beginnen mit nationalen Kodierungen.


Schauen wir uns an, wie die gemischte englische / russische Phrase „Hello world“ in drei verschiedenen Codierungssystemen angezeigt wird: Windows-1251 (russische Codierung), ISO-8859-1 (Codierungssystem für westeuropäische Sprachen), UTF-8 (Unicode) . Dieses Beispiel ist aussagekräftig, weil wir einen Satz in zwei verschiedenen Sprachen haben.



Betrachten wir nun, wie diese Codierungssysteme funktionieren und wie wir eine Textzeile von einer Codierung in eine andere übersetzen können und was passiert, wenn die Zeichen nicht richtig angezeigt werden oder wenn dies aufgrund der Unterschiede in den Systemen einfach nicht möglich ist.


Angenommen, unsere ursprüngliche Phrase wurde mit Windows-1251-Codierung geschrieben. Wenn wir uns die obige Tabelle ansehen, können wir durch die Übersetzung von Dezimalzahl oder Hexadezimalzahl in Dezimalzahl feststellen, dass wir die folgende Codierung in Binärform unter Windows-1251 erhalten.


01001000 01100101 01101100 01101100 01101111 00100000 11101100 11101000 11110000


So, jetzt haben wir die Phrase "Hallo Welt" in Windows-1251-Codierung.


Stellen Sie sich nun vor, wir haben eine Textdatei, wissen aber nicht, in welchem ​​Codierungssystem der Text gespeichert wurde. Wir gehen davon aus, dass es in ISO-8859-1 codiert ist, und öffnen es in unserem Textverarbeitungsprogramm mit diesem Codierungssystem. Wie wir zuvor gesehen haben, scheinen einige der Zeichen in Ordnung zu sein, da sie in diesem Codierungssystem existieren und sich sogar in denselben Codepunkten befinden, aber die Zeichen im russischen Wort "world" funktionieren nicht ganz so gut. Diese Zeichen sind im Codierungssystem nicht vorhanden, und an ihren Stellen oder Codepunkten finden wir in ISO-8859-1 völlig unterschiedliche Zeichen. "M" ist also Codepunkt 236, "und" ist 232 und "p" ist 240. In ISO-8859-1 entsprechen diese Codepunkte jedoch "ì" (236), "è" (232) und " ð ”(240).


Unsere in Windows-1251 codierte und in ISO-8859-1 gelesene mehrsprachige Phrase „Hello World“ sieht also wie „Hello ìèð“ aus. Wir sind teilweise kompatibel und können eine in einem System kodierte Phrase in dem anderen nicht richtig anzeigen, da die Symbole, die wir brauchen, in der zweiten Kodierung einfach nicht vorhanden sind.


Wir benötigen eine Unicode-Codierung - in unserem Fall verwenden wir UTF-8 als Beispiel. Wir haben bereits besprochen, dass Zeichen in UTF-8 zwischen 1 und 4 Byte lang sein können. Ein weiterer Vorteil ist, dass UTF im Gegensatz zu den beiden vorherigen Codierungssystemen nicht auf 256 Symbole beschränkt ist, sondern alle Symbole im Unicode-Zeichensatz enthält .


Es funktioniert ungefähr so: Das erste Bit jedes codierten Zeichens entspricht nicht dem Symbol selbst, sondern einem bestimmten Byte. Wenn also das erste Bit Null ist, wissen wir, dass das codierte Symbol nur ein Byte verwendet - was die Einstellung mit ASCII abwärtskompatibel macht. Wenn wir uns die ASCII-Symboltabelle genau ansehen, sehen wir, dass die ersten 128 Symbole (englisches Alphabet, Steuerzeichen und Interpunktionszeichen) binär ausgedrückt werden. Sie beginnen alle mit einem Bitwert von 0 (beachten Sie, dass Sie Zeichen in übersetzen) binär unter Verwendung eines Online-Konverters oder Ähnlichem kann das erste höherwertige Null-Bit verworfen werden, was etwas verwirrend sein kann.


01001000 - Das erste Bit hat den Wert 0, daher codiert 1 Byte 1 Zeichen -> „H“.


01100101 - Das erste Bit hat den Wert 0, daher codiert 1 Byte 1 Zeichen -> "e".


Wenn der Wert des ersten Bits nicht Null ist, wird das Symbol in mehreren Bytes codiert.


Eine Zwei-Byte-Codierung hat 110 für die ersten drei Bitwerte.


11010000 10111100 - Die Markierungsbits sind 110 und 10, daher verwenden wir 2 Bytes, um 1 Zeichen zu codieren. Das zweite Byte beginnt in diesem Fall immer mit „10“. Daher lassen wir die Steuerbits (die in Rot und Grün hervorgehobenen Führungsbits) aus und sehen uns den Rest des Codes an (10000111100) und konvertieren in Hex (043). -> U + 043C, was uns das russische "m" in Unicode gibt.


Die Anfangsbits für ein Drei-Byte-Zeichen sind 1110.


11101000 10000111 101010101 - Wir addieren alle Bits mit Ausnahme der Steuerbits und stellen fest, dass wir in hex 103B5, U + 103D5 - die alte persische Zahl einhundert (10000001111010101) haben.


Vier-Byte-Zeichencodierungen beginnen mit den Ableitungsbits 11110.


11110100 10001111 10111111 10111111 - U + 10FFFF, das letzte verfügbare Zeichen im Unicode-Satz (10000111111111111111111).


Jetzt können wir unsere mehrsprachige Phrase einfach in UTF-8-Codierung schreiben.


Utf-16


UTF-16 ist eine weitere Codierung mit variabler Breite. Der Hauptunterschied zwischen UTF-16 und UTF-8 besteht darin, dass UTF-16 2 Bytes (16 Bits) pro Codeeinheit anstelle von 1 Byte (8 Bits) verwendet. Mit anderen Worten, jedes in UTF-16 codierte Unicode-Zeichen kann aus zwei oder vier Bytes bestehen. Um die Sache einfach zu halten, werde ich diese beiden Bytes als Code-Einheit bezeichnen. In UTF-16 kann jedes Zeichen mit einer oder zwei Codeeinheiten dargestellt werden.


Beginnen wir mit Symbolen, die mit einer Codeeinheit codiert wurden. Wir können leicht berechnen, dass es 65.535 (216) Zeichen mit einer Codeeinheit gibt, was vollständig mit der mehrsprachigen Grundebene von Unicode übereinstimmt. Alle Zeichen in dieser Ebene werden in UTF-16 durch eine Codeeinheit (zwei Bytes) dargestellt.


Lateinischer Buchstabe "o" - 00000000 01101111.


Kyrillischer Buchstabe "M" - 00000100 00011100.


Betrachten wir nun Zeichen außerhalb der mehrsprachigen Basisebene. Diese benötigen zwei Code-Einheiten (4 Byte) und sind etwas komplizierter codiert.


Zunächst müssen wir das Konzept eines Ersatzpaares definieren. Ein Ersatzpaar besteht aus zwei Codeeinheiten, die zum Codieren eines einzelnen Zeichens verwendet werden (insgesamt 4 Bytes). Der Unicode-Zeichensatz reserviert einen speziellen Bereich von D800 bis DFFF für Ersatzpaare. Dies bedeutet, dass wir beim Konvertieren eines Ersatzpaares in hexadezimale Bytes einen Codepunkt in diesem Bereich erhalten, der eher ein Ersatzpaar als ein separates Zeichen ist.


Um ein Symbol im Bereich von 10000 bis 10FFFF zu codieren (d. H. Zeichen, die mehr als eine Codeeinheit erfordern), gehen Sie wie folgt vor:


  1. Subtrahieren Sie 10000 (hex) vom Codepunkt (dies ist der niedrigste Codepunkt im Bereich von 10000 - 10FFFF).


  2. Am Ende haben wir eine 20-Bit-Zahl, die nicht größer als FFFF ist.


  3. Die 10 höherwertigen Bits, die wir am Ende haben, werden zu D800 hinzugefügt (dem niedrigsten Codepunkt im Bereich der Ersatzpaare in Unicode).


  4. Die nächsten 10 Bits werden zu DC00 hinzugefügt (ebenfalls aus dem Ersatzpaarbereich).


  5. Als nächstes erhalten wir 2 Ersatz-16-Bit-Code-Einheiten, deren erste 6 Bits die Einheit als Teil eines Ersatzpaares definieren.


  6. Das zehnte Bit in jedem Ersatzzeichen definiert die Reihenfolge des Paares. Wenn wir eine "1" haben, ist dies das führende oder hohe Ersatzzeichen, und wenn wir eine "0" haben, ist dies das nachgestellte oder niedrige Ersatzzeichen.



Dies ist etwas sinnvoller, wenn Sie das folgende Beispiel verwenden.


Codieren und decodieren wir die persische Zahl einhundert (U + 103D5):


  1. 103D5 - 10000 = 3D5.


  2. 3D5 = 0000000000 1111010101 (die oberen 10 Bits sind Null, und wenn wir in hexadezimal konvertiert werden, erhalten wir "0" (die ersten zehn) und 3D5 (die zweiten zehn)).


  3. 0 + D800 = D800 (1101100000000000) Die ersten 6 Bits geben an, dass dieser Codepunkt in den Bereich der Ersatzpaare fällt. Das zehnte Bit (von rechts) hat den Wert "0". Dies ist also der hohe Ersatz.


  4. 3D5 + DC00 = DFD5 (1101111111010101) Die ersten 6 Bits geben an, dass dieser Codepunkt in den Bereich der Ersatzpaare fällt. Das zehnte Bit (von rechts) ist eine „1“. Wir wissen also, dass dies der niedrige Ersatz ist.


  5. Das resultierende in UTF-16 codierte Zeichen sieht aus wie - 1101100000000000 1101111111010101.



Nun wollen wir den Charakter dekodieren. Angenommen, wir haben den folgenden Codepunkt - 1101100000100010 1101111010001000:


  1. Wir konvertieren in hexadezimal = D822 DE88 (beide Codepunkte liegen im Bereich der Ersatzpaare, sodass wir wissen, dass es sich um ein Ersatzpaar handelt).


  2. 1101100000100010 - Das zehnte Bit (von rechts) ist eine „0“, dies ist also der hohe Ersatz.


  3. 1101111010001000 - Das zehnte Bit (von rechts) ist eine „1“, dies ist also der niedrige Ersatz.


  4. Wir ignorieren die 6 Bits, die angeben, dass dies ein Ersatz ist, und verbleiben bei 0000100010 1010001000 (8A88).


  5. Wir addieren 10000 (den niedrigsten Codepunkt im Ersatzbereich) 8A88 + 10000 = 18A88.


  6. Wir sehen uns die Unicode-Tabelle für U + 18A88 = Tangut Component-649 an.



Ein großes Lob an alle, die so weit gelesen haben!


Ich hoffe, das war informativ, ohne dass Sie sich zu langweilen.


Sie könnten auch nützlich finden:

Der Unicode-Zeichensatz


Strategien zur Lokalisierung von Inhalten: IP- oder browserbasiert


Über den Übersetzer


Alconost ist ein globaler Anbieter von Lokalisierungsdiensten für Apps , Spiele , Videos und Websites in über 70 Sprachen. Wir bieten Übersetzungen von muttersprachlichen Linguisten, Sprachtests, Cloud-basierte Workflows, kontinuierliche Lokalisierung, Projektmanagement rund um die Uhr und die Arbeit mit jedem Format von Zeichenkettenressourcen. Wir machen auch Werbe- und Lehrvideos und -bilder, Teaser, Erklärungen und Trailer für Google Play und den App Store.

Source: https://habr.com/ru/post/de480688/


All Articles