Co to jest „para zastępcza” w Javie?


149

Czytałem dokumentację StringBuffer, w szczególności metodę reverse () . Ta dokumentacja wspomina coś o parach zastępczych . Co to jest para zastępcza w tym kontekście? A czym są niskie i wysokie surogaty?


3
To terminologia UTF-16, wyjaśniona tutaj: download.oracle.com/javase/6/docs/api/java/lang/ ...
wkl

1
Ta metoda jest błędna: powinna odwracać pełne znaki ᴀᴋᴀ punkty kodowe - a nie oddzielne ich części, ᴀᴋᴀ jednostki kodu. Błąd polega na tym, że ta konkretna starsza metoda działa tylko na pojedynczych jednostkach char, a nie na punktach kodowych, z czego chcesz, String aby składał się z nich, a nie tylko z jednostek char. Szkoda, że ​​Java nie pozwala na użycie OO, aby to naprawić, ale zarówno Stringklasa, jak i StringBufferklasy zostały finalizizowane. Powiedz, czy to nie eufemizm na określenie zabitych? :)
tchrist

2
@tchrist Dokumentacja (i źródło) mówi, że odwraca się jako ciąg punktów kodowych. (Prawdopodobnie 1.0.2 tego nie zrobił i nigdy nie dostaniesz takiej zmiany zachowania w dzisiejszych czasach.)
Tom Hawtin - tackline

Odpowiedzi:


127

Termin „para zastępcza” odnosi się do sposobu kodowania znaków Unicode z wysokimi punktami kodowymi w schemacie kodowania UTF-16.

W kodowaniu znaków Unicode znaki są mapowane na wartości z przedziału od 0x0 do 0x10FFFF.

Wewnętrznie Java używa schematu kodowania UTF-16 do przechowywania ciągów tekstu Unicode. W UTF-16 używane są 16-bitowe (dwubajtowe) jednostki kodu. Ponieważ 16 bitów może zawierać tylko zakres znaków od 0x0 do 0xFFFF, do przechowywania wartości powyżej tego zakresu (0x10000 do 0x10FFFF) używana jest dodatkowa złożoność. Odbywa się to za pomocą par jednostek kodu znanych jako surogaty.

Jednostki kodu zastępczego znajdują się w dwóch zakresach zwanych „dużymi surogatami” i „małymi surogatami”, w zależności od tego, czy są dozwolone na początku, czy na końcu sekwencji dwóch jednostek kodu.


4
ten ma najwięcej głosów, ale nie zawiera ani jednego przykładu kodu. Nie ma też żadnej z tych odpowiedzi na temat tego, jak go używać. Dlatego jest to dyskutowane.
George Xavier

57

Wczesne wersje Java reprezentowały znaki Unicode przy użyciu 16-bitowego typu danych char. Ten projekt miał sens w tamtym czasie, ponieważ wszystkie znaki Unicode miały wartości mniejsze niż 65 535 (0xFFFF) i mogły być reprezentowane w 16 bitach. Później jednak Unicode zwiększył wartość maksymalną do 1114111 (0x10FFFF). Ponieważ wartości 16-bitowe były zbyt małe, aby reprezentować wszystkie znaki Unicode w standardzie Unicode 3.1, wartości 32-bitowe - zwane punktami kodowymi - zostały przyjęte do schematu kodowania UTF-32. Jednak wartości 16-bitowe są preferowane zamiast wartości 32-bitowych w celu efektywnego wykorzystania pamięci, dlatego Unicode wprowadził nowy projekt, aby umożliwić dalsze używanie wartości 16-bitowych. Ten projekt, przyjęty w schemacie kodowania UTF-16, przypisuje 1024 wartości do 16-bitowych wysokich surogatów (w zakresie U + D800 do U + DBFF), a kolejne 1024 wartości do 16-bitowych niskich surogatów (w zakresie U + DC00 do U + DFFF).


7
Podoba mi się to bardziej niż zaakceptowana odpowiedź, ponieważ wyjaśnia, w jaki sposób Unicode 3.1 zarezerwował 1024 + 1024 (wysokie + niskie) wartości z oryginalnych 65535, aby uzyskać 1024 * 1024 nowych wartości, bez dodatkowych wymagań, aby parsery rozpoczynały się na początku strunowy.
Eric Hirst

1
Nie podoba mi się ta odpowiedź, która sugeruje, że UTF-16 jest najbardziej wydajnym kodowaniem Unicode. UTF-8 istnieje i nie renderuje większości tekstu jako dwa bajty. UTF-16 jest dziś najczęściej używany, ponieważ Microsoft wybrał go, zanim UTF-32 stał się rzeczą, a nie ze względu na wydajność pamięci. Prawie jedyny czas, w którym naprawdę chcesz, aby UTF-16 był wykonywany w systemie Windows, a więc zarówno czytasz, jak i piszesz dużo. W przeciwnym razie UTF-32 dla dużej prędkości (stałe przesunięcia b / c) lub UTF-8 dla małej ilości pamięci (b / c minimum 1 bajt)
Pozew Moniki w sprawie Funduszu

23

Dokumentacja mówi, że nieprawidłowe ciągi znaków UTF-16 mogą stać się poprawne po wywołaniu reversemetody, ponieważ mogą być one odwrotnością prawidłowych łańcuchów. Para zastępcza (omówiona tutaj ) to para 16-bitowych wartości w UTF-16, które kodują pojedynczy punkt kodowy Unicode; niskie i wysokie surogaty to dwie połowy tego kodowania.


6
Wyjaśnienie. Ciąg musi być odwrócony na „prawdziwych” znakach (zwanych również „grafemami” lub „elementami tekstowymi”). Pojedynczy „znakowy” punkt kodowy może być jednym lub dwoma „znakami” fragmentami (parą zastępczą), a grafemem może być jeden lub więcej z tych punktów kodowych (tj. Podstawowy kod znaku plus jeden lub więcej łączonych kodów znaków, z których każdy może zawierać jeden lub dwa fragmenty 16-bitowe lub długie „znaki”). Zatem pojedynczy grafem może składać się z trzech połączonych znaków, z których każdy ma długość dwóch „znaków”, co daje łącznie 6 „znaków”. Podczas odwracania całego ciągu znaków wszystkie 6 znaków należy zachować razem, w kolejności (tj. Nieodwrócone).
Triynko,

4
Dlatego typ danych „char” jest raczej mylący. „charakter” to pojęcie luźne. Typ „char” to w rzeczywistości rozmiar fragmentu UTF16 i nazywamy go znakiem ze względu na względną rzadkość występujących par zastępczych (tj. Zazwyczaj reprezentuje cały punkt kodowy znaku), więc „znak” tak naprawdę odnosi się do pojedynczego punktu kodu Unicode , ale z drugiej strony łącząc znaki, możesz mieć sekwencję znaków wyświetlaną jako pojedynczy „znak / element grafem / tekst”. To nie jest fizyka jądrowa; koncepcje są proste, ale język jest zagmatwany.
Triynko,

W czasie, gdy Java była rozwijana, Unicode był w powijakach. Java istniała przez około 5 lat, zanim Unicode uzyskał zastępcze pary, więc 16-bitowy znak pasował całkiem dobrze w tamtym czasie. Teraz znacznie lepiej jest używać UTF-8 i UTF-32 niż UTF-16.
Jonathan Baldwin

23

Dodanie więcej informacji do powyższych odpowiedzi z tego postu.

Testowany w Javie-12, powinien działać we wszystkich wersjach Java powyżej 5.

Jak wspomniano tutaj: https://stackoverflow.com/a/47505451/2987755 ,
którykolwiek znak (którego Unicode jest powyżej U + FFFF) jest reprezentowany jako para zastępcza, którą Java przechowuje jako parę wartości znaków, tj. Pojedynczy Unicode znak jest reprezentowany jako dwa sąsiednie znaki Java.
Jak widać na poniższym przykładzie.
1. Długość:

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

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

2. Równość:
Przedstaw "🌉" jako łańcuch znaków używając Unicode, \ud83c\udf09jak poniżej i sprawdź równość.

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

Java nie obsługuje UTF-32

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

3. Możesz przekonwertować znak Unicode na ciąg Java

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

4. String.substring () nie uwzględnia znaków uzupełniających

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

Aby rozwiązać ten problem, możemy użyć String.offsetByCodePoints(int index, int codePointOffset)

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

5. Ciąg Iteracja Unicode z BreakIterator
6. Sortowanie Struny z Unicode java.text.Collator
7. bohatera toUpperCase(), toLowerCase()nie powinny być stosowane metody, zamiast, wielkie i małe litery użycie String poszczególnych lokalizacji.
8. Character.isLetter(char ch)nie obsługuje, lepiej wykorzystywanych Character.isLetter(int codePoint), dla każdej methodName(char ch)metody w klasie Character zostanie określony typ, methodName(int codePoint)który może obsługiwać dodatkowe znaki.
9. Określ zestaw znaków w String.getBytes(), konwertując z bajtów na łańcuch,InputStreamReader ,OutputStreamWriter

Odniesienie:
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

Więcej informacji na temat przykładu image1 image2
Inne terminy, które warto poznać: Normalizacja , BiDi


2
zalogowałem się specjalnie, aby zagłosować na tę odpowiedź (mam na myśli zmianę okna z incognito na normalne: P). Najlepsze wyjaśnienie dla nooba
N-JOY

1
Dziękuję! Cieszę się, że pomogło, ale autor oryginalnego postu zasługuje na wszelkie uznanie.
dkb

Świetne przykłady! Zalogowałem się, żeby też zagłosować :) I znowu pomyślałem (znowu), że naprawdę nie rozumiem, dlaczego Java utrzymuje żywe ZNANE błędy w swoim kodzie. Całkowicie szanuję, że nie chcą łamać istniejącego kodu, ale daj spokój ... ile godzin zostało straconych na pracy nad tymi błędami? Jeśli jest zepsuty, napraw to, do cholery!
Franz D.


6

Mała przedmowa

  • Unicode reprezentuje punkty kodowe. Każdy punkt kodowy może być zakodowany w blokach 8-, 16- lub 32-bitowych zgodnie ze standardem Unicode.
  • Przed wersją 3.1 najczęściej używane było kodowanie 8-bitowe, znane jako UTF-8, i kodowanie 16-bitowe, znane jako UCS-2 lub „Uniwersalny zestaw znaków zakodowany w 2 oktetach”. UTF-8 koduje punkty Unicode jako sekwencję bloków 1-bajtowych, podczas gdy UCS-2 zawsze zajmuje 2 bajty:

    A = 41 - jeden blok 8-bitowy z UTF-8
    A = 0041 - jeden blok 16-bitowy z UCS-2
    Ω = CE A9 - dwa bloki 8-bitowe z UTF-8
    Ω = 03A9 - jeden blok 16-bitowe z UCS-2

Problem

Konsorcjum uważało, że 16 bitów wystarczyłoby na pokrycie dowolnego języka czytelnego dla człowieka, co daje 2 ^ 16 = 65536 możliwych wartości kodu. Było to prawdą dla Plane 0, znanego również jako BPM lub Basic Multilingual Plane, który zawiera obecnie 55.445 z 65536 punktów kodowych. BPM obejmuje prawie każdy ludzki język na świecie, w tym symbole chińsko-japońsko-koreańskie (CJK).

Minął czas i dodano nowe azjatyckie zestawy znaków, same symbole chińskie zajęły ponad 70 000 punktów. Teraz są nawet w standardzie punkty Emoji 😺. Dodano nowe 16 „dodatkowych” samolotów . Pomieszczenie UCS-2 nie wystarczyło do pokrycia czegokolwiek większego niż Samolot-0.

Decyzja Unicode

  1. Ogranicz Unicode do 17 płaszczyzn × 65 536 znaków na płaszczyznę = 1 114 112 maksymalnie punktów.
  2. Obecny UTF-32, dawniej znany jako UCS-4, do przechowywania 32-bitów dla każdego punktu kodowego i pokrycia wszystkich płaszczyzn.
  3. Nadal używaj UTF-8 jako kodowania dynamicznego, ogranicz UTF-8 do maksymalnie 4 bajtów dla każdego punktu kodowego, tj. Od 1 do 4 bajtów na punkt.
  4. Wycofaj UCS-2
  5. Utwórz UTF-16 w oparciu o UCS-2. Uczyń UTF-16 dynamicznym, więc zajmuje 2 bajty lub 4 bajty na punkt. Przypisz 1024 punkty U + D800 – U + DBFF, zwane wysokimi surogatami, do UTF-16; przypisz 1024 symbole U + DC00 – U + DFFF, nazywane małymi surogatami, do UTF-16.

    Z tymi zmianami, BPM jest pokryta 1 blokiem 16 bitów w UTF-16, podczas gdy wszystkie „znaki uzupełniające” są pokryte parami zastępczymi, które zawierają 2 bloki po 16 bitów każdy, łącznie 1024x1024 = 1 048 576 punktów.

    Wysoki surogat poprzedza niski surogat . Każde odstępstwo od tej zasady jest uważane za złe kodowanie. Na przykład surogat bez pary jest niepoprawny, niski surogat stojący przed wysokim surogatem jest nieprawidłowy.

    𝄞, 'MUSICAL SYMBOL G CLEF', jest zakodowany w UTF-16 jako para surogatów 0xD834 0xDD1E (2 na 2 bajty),
    w UTF-8 jako 0xF0 0x9D 0x84 0x9E (4 na 1 bajt),
    w UTF-32 jako 0x0001D11E (1 na 4 bajty).

Obecna sytuacja

  • Chociaż zgodnie ze standardem surogaty są specjalnie przypisywane tylko do UTF-16, historycznie niektóre aplikacje Windows i Java wykorzystywały punkty UTF-8 i UCS-2 zarezerwowane teraz dla zakresu zastępczego.
    Aby obsługiwać starsze aplikacje z nieprawidłowym kodowaniem UTF-8 / UTF-16, utworzono nowy standard WTF-8 , Wobbly Transformation Format. Obsługuje dowolne punkty zastępcze, takie jak niesparowany surogat lub niepoprawna sekwencja. Obecnie niektóre produkty nie są zgodne z normą i traktują UTF-8 jako WTF-8.
  • Rozwiązanie zastępcze otworzyło wiele problemów związanych z bezpieczeństwem podczas konwersji między różnymi kodowaniami, większość z nich została dobrze rozwiązana.

Aby podążać za tym tematem, usunięto wiele szczegółów historycznych ⚖.
Najnowszy standard Unicode można znaleźć pod adresem http://www.unicode.org/versions/latest


3

Para zastępcza to dwie „jednostki kodowe” w UTF-16, które tworzą jeden „punkt kodowy”. Dokumentacja Javy stwierdza, że ​​te „punkty kodowe” będą nadal ważne, a ich „jednostki kodowe” będą poprawnie uporządkowane, po odwrotnej kolejności. Ponadto stwierdza, że ​​dwie niesparowane jednostki kodu zastępczego mogą być odwrócone i tworzyć prawidłową parę zastępczą. Co oznacza, że ​​jeśli istnieją niesparowane jednostki kodu, istnieje szansa, że ​​odwrotna strona może nie być taka sama!

Zauważ jednak, że dokumentacja nie mówi nic o grafemach - które są połączonymi wieloma punktami kodowymi. Oznacza to, że e i akcent, który się z nim łączy, mogą być nadal przełączane, umieszczając w ten sposób akcent przed e. Co oznacza, że ​​jeśli jest inna samogłoska przed e, może uzyskać akcent z e.

Yikes!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.