Was ist ein "Ersatzpaar" in Java?


149

Ich habe die Dokumentation gelesen StringBuffer, insbesondere für die reverse () -Methode. Diese Dokumentation erwähnt etwas über Ersatzpaare . Was ist in diesem Zusammenhang ein Ersatzpaar? Und was sind niedrige und hohe Ersatzzeichen?


3
Es ist UTF-16 Terminologie, hier erklärt: download.oracle.com/javase/6/docs/api/java/lang/…
wkl

1
Diese Methode ist fehlerhaft: Sie sollte vollständige Zeichen umkehren - Codepunkte - keine getrennten Teile davon, Codeeinheiten. Der Fehler besteht darin, dass diese bestimmte Legacy-Methode nur für einzelne Zeicheneinheiten und nicht für Codepunkte funktioniert. Daraus möchten Sie bestehen String, nicht nur für Zeicheneinheiten. Schade, dass Sie mit Java OO nicht verwenden können, um dies zu beheben, aber sowohl die StringKlasse als auch die StringBufferKlassen wurden finalisiert. Sag mal, ist das nicht ein Euphemismus für Getötete? :)
tchrist

2
@tchrist Die Dokumentation (und Quelle) besagt, dass es sich um eine Folge von Codepunkten handelt. (Vermutlich hat 1.0.2 das nicht getan, und Sie würden heutzutage nie eine solche Verhaltensänderung bekommen.)
Tom Hawtin - Tackline

Antworten:


128

Der Begriff "Ersatzpaar" bezieht sich auf ein Mittel zum Codieren von Unicode-Zeichen mit hohen Codepunkten im UTF-16-Codierungsschema.

Bei der Unicode-Zeichencodierung werden Zeichen Werten zwischen 0x0 und 0x10FFFF zugeordnet.

Intern verwendet Java das UTF-16-Codierungsschema, um Zeichenfolgen von Unicode-Text zu speichern. In UTF-16 werden 16-Bit-Codeeinheiten (zwei Byte) verwendet. Da 16 Bit nur den Zeichenbereich von 0x0 bis 0xFFFF enthalten können, wird eine zusätzliche Komplexität verwendet, um Werte über diesem Bereich (0x10000 bis 0x10FFFF) zu speichern. Dies erfolgt mit Paaren von Codeeinheiten, die als Ersatzeinheiten bezeichnet werden.

Die Ersatzcodeeinheiten befinden sich in zwei Bereichen, die als "hohe Surrogate" und "niedrige Surrogate" bezeichnet werden, je nachdem, ob sie zu Beginn oder am Ende der Sequenz mit zwei Codeeinheiten zulässig sind.


4
Dies hat die meisten Stimmen, bietet jedoch kein einziges Codebeispiel. Keine dieser Antworten darüber, wie man es tatsächlich benutzt. Deshalb wird dies abgelehnt.
George Xavier

57

Frühe Java-Versionen stellten Unicode-Zeichen mit dem 16-Bit-Char-Datentyp dar. Dieses Design war zu dieser Zeit sinnvoll, da alle Unicode-Zeichen Werte unter 65.535 (0xFFFF) hatten und in 16 Bit dargestellt werden konnten. Später erhöhte Unicode den Maximalwert jedoch auf 1.114.111 (0x10FFFF). Da 16-Bit-Werte zu klein waren, um alle Unicode-Zeichen in Unicode Version 3.1 darzustellen, wurden 32-Bit-Werte - sogenannte Codepunkte - für das UTF-32-Codierungsschema übernommen. Für eine effiziente Speichernutzung werden jedoch 16-Bit-Werte gegenüber 32-Bit-Werten bevorzugt. Daher hat Unicode ein neues Design eingeführt, um die weitere Verwendung von 16-Bit-Werten zu ermöglichen. Dieses Design, das im UTF-16-Codierungsschema übernommen wurde, weist 16-Bit-Hochsurrogaten (im Bereich von U + D800 bis U + DBFF) 1.024 Werte und 16-Bit-Niedrigsurrogaten (im Bereich U + DC00) weitere 1.024 Werte zu zu U + DFFF).


7
Ich mag dies besser als die akzeptierte Antwort, da es erklärt, wie Unicode 3.1 1024 + 1024 (hoch + niedrig) Werte aus dem ursprünglichen 65535 reserviert, um 1024 * 1024 neue Werte zu erhalten, ohne zusätzliche Anforderungen, die Parser am Anfang von a beginnen Zeichenfolge.
Eric Hirst

1
Ich mag diese Antwort nicht, weil sie impliziert, dass UTF-16 die speichereffizienteste Unicode-Codierung ist. UTF-8 vorhanden ist , und nicht die meisten Text als zwei Bytes übertragen. UTF-16 wird heute meistens verwendet, weil Microsoft es ausgewählt hat, bevor UTF-32 eine Sache war, nicht aus Gründen der Speichereffizienz. Das einzige Mal, dass Sie UTF-16 wirklich wollen, ist, wenn Sie unter Windows viel mit Dateien umgehen und es daher viel lesen und schreiben. Andernfalls UTF-32 für hohe Geschwindigkeit (b / c konstante Offsets) oder UTF-8 für niedrigen Speicher (b / c mindestens 1 Byte)
Fund Monica's Lawsuit

23

In dieser Dokumentation heißt es, dass ungültige UTF-16-Zeichenfolgen nach dem Aufrufen der reverseMethode möglicherweise gültig werden, da sie möglicherweise die Umkehrung gültiger Zeichenfolgen darstellen. Ein Ersatzpaar ( hier erläutert ) ist ein Paar von 16-Bit-Werten in UTF-16, die einen einzelnen Unicode-Codepunkt codieren. Die niedrigen und hohen Surrogate sind die beiden Hälften dieser Codierung.


6
Klärung. Eine Zeichenfolge muss bei "wahren" Zeichen (auch als "Grapheme" oder "Textelemente" bezeichnet) umgekehrt werden. Ein einzelner "Zeichen" -Codepunkt kann ein oder zwei "Zeichen" -Blöcke (Ersatzpaar) sein, und ein Graphem kann einer oder mehrere dieser Codepunkte sein (dh ein Basiszeichencode plus ein oder mehrere kombinierende Zeichencodes, von denen jeder kann ein oder zwei 16-Bit-Chunks oder "Zeichen" lang sein). Ein einzelnes Graphem könnte also aus drei kombinierten Zeichen bestehen, die jeweils zwei "Zeichen" lang sind und insgesamt 6 "Zeichen" ergeben. Alle 6 "Zeichen" müssen in der Reihenfolge (dh nicht umgekehrt) zusammengehalten werden, wenn die gesamte Zeichenfolge umgekehrt wird.
Triynko

4
Daher ist der Datentyp "char" eher irreführend. "Charakter" ist ein loser Begriff. Der Typ "char" ist eigentlich nur die UTF16-Blockgröße und wir nennen ihn Zeichen, da relativ selten Ersatzpaare auftreten (dh er repräsentiert normalerweise einen ganzen Zeichencodepunkt), sodass sich "Zeichen" wirklich auf einen einzelnen Unicode-Codepunkt bezieht Mit den kombinierten Zeichen können Sie jedoch eine Folge von Zeichen erstellen, die als einzelnes "Zeichen / Graphem / Textelement" angezeigt werden. Dies ist keine Raketenwissenschaft; Die Konzepte sind einfach, aber die Sprache ist verwirrend.
Triynko

Zu der Zeit, als Java entwickelt wurde, steckte Unicode noch in den Kinderschuhen. Java gab es ungefähr 5 Jahre lang, bevor Unicode Ersatzpaare bekam, so dass ein 16-Bit-Zeichen zu dieser Zeit ziemlich gut passte. Jetzt sind Sie mit UTF-8 und UTF-32 viel besser dran als mit UTF-16.
Jonathan Baldwin

23

Hinzufügen mehr Informationen zu den oben genannten Antworten von diesem Beitrag.

In Java-12 getestet, sollte in allen Java-Versionen über 5 funktionieren.

Wie hier erwähnt: https://stackoverflow.com/a/47505451/2987755 ,
welches Zeichen (dessen Unicode über U + FFFF liegt) als Ersatzpaar dargestellt wird, das Java als Paar von Zeichenwerten speichert, dh als einzelner Unicode Zeichen wird als zwei benachbarte Java-Zeichen dargestellt.
Wie wir im folgenden Beispiel sehen können.
1. Länge:

"🌉".length()  //2, Expectations was it should return 1

"🌉".codePointCount(0,"🌉".length())  //1, To get the number of Unicode characters in a Java String  

2. Gleichheit:
Stellen Sie "🌉" für String mit Unicode \ud83c\udf09wie folgt dar und überprüfen Sie die Gleichheit.

"🌉".equals("\ud83c\udf09") // true

Java unterstützt UTF-32 nicht

"🌉".equals("\u1F309") // false  

3. Sie können Unicode-Zeichen in Java-Zeichenfolgen konvertieren

"🌉".equals(new String(Character.toChars(0x0001F309))) //true

4. String.substring () berücksichtigt keine zusätzlichen Zeichen

"🌉🌐".substring(0,1) //"?"
"🌉🌐".substring(0,2) //"🌉"
"🌉🌐".substring(0,4) //"🌉🌐"

Um dies zu lösen, können wir verwenden String.offsetByCodePoints(int index, int codePointOffset)

"🌉🌐".substring(0,"🌉🌐".offsetByCodePoints(0,1) // "🌉"
"🌉🌐".substring(2,"🌉🌐".offsetByCodePoints(1,2)) // "🌐"

5. Iterieren Unicode - String mit BreakIterator
6. Sortierung Strings mit Unicode java.text.Collator
7. Charakters toUpperCase(), toLowerCase()sollten Methoden nicht verwendet werden, stattdessen Verwendung String Groß- und Klein besonders locale.
8. Character.isLetter(char ch)unterstützt nicht, besser verwendet Character.isLetter(int codePoint), für jede methodName(char ch)Methode in der Zeichenklasse gibt es einen Typ, der methodName(int codePoint)zusätzliche Zeichen verarbeiten kann.
Geben charset 9 String.getBytes()von Bytes zu String Umwandeln InputStreamReader,OutputStreamWriter

Ref:
https://coolsymbol.com/emojis/emoji-for-copy-and-paste.html#objects
https://www.online-toolz.com/tools/text-unicode-entities-convertor.php
https: //www.ibm.com/developerworks/library/j-unicode/index.html
https://www.oracle.com/technetwork/articles/javaee/supplementary-142654.html

Weitere Informationen zu Beispiel image1 image2
Weitere Begriffe, die es wert sind, untersucht zu werden: Normalisierung , BiDi


2
speziell angemeldet, um für diese Antwort zu stimmen (ich meine, das Fenster wurde von inkognito auf normal geändert: P). Beste Erklärung für einen Noob
N-JOY

1
Vielen Dank! Ich bin froh, dass es geholfen hat, aber der ursprüngliche Postautor verdient die ganze Anerkennung.
dkb

Tolle Beispiele! Ich habe mich angemeldet, um es auch zu verbessern :) Und wieder dachte ich (wieder), dass ich wirklich nicht verstehe, warum Java bekannte Fehler in ihrem Code am Leben erhält. Ich respektiere es total, dass sie keinen vorhandenen Code brechen wollen, aber komm schon ... wie viele Stunden sind verloren gegangen, um diese Fehler zu umgehen? Wenn es kaputt ist, reparieren Sie es, verdammt!
Franz D.


6

Kleines Vorwort

  • Unicode repräsentiert Codepunkte. Jeder Codepunkt kann gemäß dem Unicode-Standard in 8-, 16- oder 32-Bit-Blöcken codiert werden.
  • Vor der Version 3.1 wurden hauptsächlich 8-Bit-Codierungen (UTF-8) und 16-Bit-Codierungen (UCS-2 oder „Universal Character Set coded in 2 octets“) verwendet. UTF-8 codiert Unicode-Punkte als Folge von 1-Byte-Blöcken, während UCS-2 immer 2 Byte benötigt:

    A = 41 - ein 8-Bit-Block mit UTF-8
    A = 0041 - ein 16-Bit-Block mit UCS-2
    Ω = CE A9 - zwei 8-Bit-Blöcke mit UTF-8
    Ω = 03A9 - ein Block von 16 Bit mit UCS-2

Problem

Das Konsortium war der Ansicht, dass 16 Bit ausreichen würden, um jede für Menschen lesbare Sprache abzudecken, was 2 ^ 16 = 65536 mögliche Codewerte ergibt. Dies galt für die Ebene 0, auch bekannt als BPM oder Basic Multilingual Plane, die heute 55.445 von 65536 Codepunkten enthält. BPM deckt fast alle menschlichen Sprachen der Welt ab, einschließlich chinesisch-japanisch-koreanischer Symbole (CJK).

Die Zeit verging und neue asiatische Zeichensätze wurden hinzugefügt, chinesische Symbole nahmen allein mehr als 70.000 Punkte ein. Jetzt gibt es sogar Emoji-Punkte als Teil des Standards 😺. Neue 16 "zusätzliche" Flugzeuge wurden hinzugefügt. Der UCS-2-Raum reichte nicht aus, um etwas Größeres als Flugzeug-0 abzudecken.

Unicode-Entscheidung

  1. Begrenzen Sie Unicode auf 17 Ebenen × 65 536 Zeichen pro Ebene = 1 114 112 maximale Punkte.
  2. Präsentieren Sie UTF-32, früher bekannt als UCS-4, um 32 Bit für jeden Codepunkt zu speichern und alle Ebenen abzudecken.
  3. Verwenden Sie UTF-8 weiterhin als dynamische Codierung. Begrenzen Sie UTF-8 auf maximal 4 Byte für jeden Codepunkt, dh von 1 bis 4 Byte pro Punkt.
  4. Veraltet UCS-2
  5. Erstellen Sie UTF-16 basierend auf UCS-2. Machen Sie UTF-16 dynamisch, sodass 2 Bytes oder 4 Bytes pro Punkt erforderlich sind. Weisen Sie UTF-16 1024 Punkte U + D800 - U + DBFF zu, die als High Surrogates bezeichnet werden. Weisen Sie UTF-16 1024 Symbole U + DC00 - U + DFFF zu, die als Low Surrogates bezeichnet werden.

    Mit diesen Änderungen wird BPM mit 1 Block von 16 Bits in UTF-16 bedeckt, während alle „Zusatzzeichen“ abgedeckt sind mit Surrogatpaaren präsentierenden 2 Blöcke von jeweils 16 Bits, total 1024x1024 = 1 048 576 Punkte.

    Ein hoher Ersatz geht einem niedrigen Ersatz voraus . Jede Abweichung von dieser Regel wird als fehlerhafte Codierung angesehen. Zum Beispiel ist ein Ersatz ohne Paar falsch, ein niedriger Ersatz, der vor einem hohen Ersatz steht, ist falsch.

    𝄞, 'MUSICAL SYMBOL G CLEF', wird in UTF-16 als Ersatzpaar 0xD834 0xDD1E (2 x 2 Byte),
    in UTF-8 als 0xF0 0x9D 0x84 0x9E (4 x 1 Byte),
    in UTF-32 als codiert 0x0001D11E (1 x 4 Byte).

Momentane Situation

  • Obwohl die Ersatzzeichen gemäß dem Standard speziell nur UTF-16 zugewiesen sind, verwendeten in der Vergangenheit einige Windows- und Java-Anwendungen UTF-8- und UCS-2-Punkte, die jetzt dem Ersatzbereich vorbehalten sind.
    Um ältere Anwendungen mit falschen UTF-8 / UTF-16-Codierungen zu unterstützen, wurde ein neues Standard- WTF-8 , Wobbly Transformation Format, erstellt. Es unterstützt beliebige Ersatzpunkte, z. B. einen nicht gepaarten Ersatzpunkt oder eine falsche Sequenz. Heutzutage entsprechen einige Produkte nicht dem Standard und behandeln UTF-8 als WTF-8.
  • Die Ersatzlösung eröffnete viele Sicherheitsprobleme bei der Konvertierung zwischen verschiedenen Codierungen, von denen die meisten gut behandelt wurden.

Viele historische Details wurden unterdrückt, um dem Thema zu folgen ⚖.
Den neuesten Unicode-Standard finden Sie unter http://www.unicode.org/versions/latest


3

Ein Ersatzpaar besteht aus zwei 'Codeeinheiten' in UTF-16, die einen 'Codepunkt' bilden. In der Java-Dokumentation heißt es, dass diese "Codepunkte" weiterhin gültig sind und ihre "Codeeinheiten" nach der umgekehrten Reihenfolge korrekt angeordnet sind. Es heißt weiter, dass zwei ungepaarte Ersatzcodeeinheiten umgekehrt werden können und ein gültiges Ersatzpaar bilden. Das heißt, wenn es ungepaarte Codeeinheiten gibt, besteht die Möglichkeit, dass die Umkehrung der Umkehrung nicht dieselbe ist!

Beachten Sie jedoch, dass die Dokumentation nichts über Grapheme aussagt - bei denen es sich um mehrere Codepunkte zusammen handelt. Was bedeutet, dass e und der dazugehörige Akzent möglicherweise noch umgeschaltet werden, wodurch der Akzent vor dem e platziert wird. Das heißt, wenn es vor dem e einen anderen Vokal gibt, kann er den Akzent erhalten, der auf dem e war.

Huch!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.