Что такое «суррогатная пара» в Java?

149

Я читал документацию для StringBuffer, в частности, метода reverse () . Эта документация упоминает что-то о суррогатных парах . Что такое суррогатная пара в этом контексте? А что такое низкие и высокие суррогаты?

Раймонд
источник
3
Это терминология UTF-16, объясненная здесь: download.oracle.com/javase/6/docs/api/java/lang/…
wkl
1
Этот метод содержит ошибки: он должен инвертировать полные символы ᴀᴋᴀ кодовые точки, а не отдельные их части ᴀᴋᴀ кодовые единицы. Ошибка заключается в том, что этот конкретный унаследованный метод работает только с отдельными символьными единицами, а не с кодовыми точками, из которых вы хотите String сделать, а не только символьные. Жаль, что Java не позволяет вам использовать OO, чтобы исправить это, но и Stringкласс, и StringBufferклассы были finalизменены. Скажите, разве это не эвфемизм для убитых? :)
tchrist
2
@tchrist В документации (и источнике) говорится, что она переворачивается как строка кодов. (Предположительно, 1.0.2 этого не делал, и в наши дни вы никогда не получите такого изменения поведения.)
Том Хотин - tackline

Ответы:

127

Термин «суррогатная пара» относится к средству кодирования символов Unicode с высокими кодовыми точками в схеме кодирования UTF-16.

В кодировке Unicode символы отображаются в значения между 0x0 и 0x10FFFF.

Внутренне Java использует схему кодирования UTF-16 для хранения строк текста Unicode. В UTF-16 используются 16-битные (двухбайтовые) кодовые единицы. Поскольку 16 бит могут содержать только диапазон символов от 0x0 до 0xFFFF, некоторая дополнительная сложность используется для хранения значений выше этого диапазона (от 0x10000 до 0x10FFFF). Это делается с помощью пар кодовых единиц, известных как суррогаты.

Единицы суррогатного кода находятся в двух диапазонах, известных как «высокие суррогаты» и «низкие суррогаты», в зависимости от того, разрешены ли они в начале или в конце последовательности из двух единиц кода.

Джеффри Л Уитледж
источник
4
это имеет наибольшее количество голосов, но не дает ни одного примера кода. Ни один из этих ответов о том, как на самом деле его использовать. Вот почему это понижается.
Джордж Ксавье
57

Ранние версии Java представляли символы Unicode, используя 16-битный тип данных char. В то время этот дизайн имел смысл, потому что все символы Юникода имели значения меньше 65 535 (0xFFFF) и могли быть представлены в 16 битах. Позже, однако, Unicode увеличил максимальное значение до 1,114,111 (0x10FFFF). Поскольку 16-разрядные значения были слишком малы для представления всех символов Unicode в версии 3.1 Unicode, 32-разрядные значения, называемые кодовыми точками, были приняты для схемы кодирования UTF-32. Но 16-битные значения предпочтительнее 32-битных для эффективного использования памяти, поэтому Unicode представил новый дизайн, позволяющий продолжать использовать 16-битные значения. Эта конструкция, принятая в схеме кодирования UTF-16, присваивает 1024 значения 16-разрядным старшим суррогатам (в диапазоне U + D800 - U + DBFF) и еще 1024 значения 16-разрядным старшим суррогатам (в диапазоне U + DC00) до U + DFFF).

Ибрагем Шаббан
источник
7
Мне нравится этот ответ лучше, чем принятый ответ, поскольку он объясняет, как Unicode 3.1 зарезервировал 1024 + 1024 (high + low) значения из исходных 65535, чтобы получить 1024 * 1024 новых значения, без каких-либо дополнительных требований, которые парсеры запускают в начале строка.
Эрик Херст
1
Мне не нравится этот ответ, поскольку подразумевается, что UTF-16 - это наиболее эффективная для памяти кодировка Unicode. UTF-8 существует и не отображает большую часть текста в виде двух байтов. UTF-16 в основном используется сегодня, потому что Microsoft выбрала его раньше, чем UTF-32, а не для эффективности памяти. Единственный случай, когда вам действительно захочется UTF-16, - это когда вы много работаете с файлами в Windows, и поэтому много читаете и пишете. В противном случае, UTF-32 для высокой скорости (постоянные смещения b / c) или UTF-8 для малой памяти (минимум 1 байт / c)
Fund Monica's Lawsuit
23

Эта документация говорит о том, что недопустимые строки UTF-16 могут стать действительными после вызова reverseметода, поскольку они могут быть обращены к действительным строкам. Суррогатная пара (обсуждаемая здесь ) - это пара 16-битных значений в UTF-16, которые кодируют одну кодовую точку Unicode; низкие и высокие суррогаты - это две половины этого кодирования.

Иеремия Уиллкок
источник
6
Разъяснение. Строка должна быть обращена к «истинным» символам (так называемые «графемы» или «текстовые элементы»). Одна «символьная» кодовая точка может представлять собой один или два «символьных» блока (суррогатная пара), а графема может представлять собой одну или несколько из этих кодовых точек (т. Е. Базовый код символа плюс один или несколько кодов комбинации символов, каждый из которых может быть один или два 16-битных фрагмента или "символы" длиной). Таким образом, одна графема может состоять из трех комбинирующих символов, каждый из которых состоит из двух символов, всего 6 символов. Все 6 "символов" должны храниться вместе, по порядку (т.е. не переворачиваться), при обращении всей строки символов.
Триынко
4
Следовательно, тип данных «char» довольно обманчив. «характер» это свободный термин. Тип "char" - это на самом деле просто размер фрагмента UTF16, и мы называем его символом из-за относительной редкости встречающихся суррогатных пар (т. Е. Обычно он представляет собой целую символьную кодовую точку), поэтому "символ" действительно относится к одной кодовой точке Unicode , но затем с помощью комбинированных символов вы можете иметь последовательность символов, которые отображаются как один «символ / графема / текстовый элемент». Это не ракетостроение; понятия просты, но язык сбивает с толку.
Триынко
На момент разработки Java Unicode находился в зачаточном состоянии. Ява существовала около 5 лет, пока Unicode не приобрела суррогатные пары, поэтому 16-битный символ в то время очень хорошо подходил. Теперь вам гораздо лучше использовать UTF-8 и UTF-32, чем UTF-16.
Джонатан Болдуин
23

Добавление дополнительной информации к ответам выше из этого поста.

Протестировано в Java-12, должно работать во всех версиях Java выше 5.

Как упомянуто здесь: https://stackoverflow.com/a/47505451/2987755 ,
какой символ (чей Unicode выше U + FFFF) представлен как суррогатная пара, которую Java хранит в виде пары значений char, то есть одного Unicode символ представлен в виде двух смежных символов Java.
Как мы можем видеть в следующем примере.
1. Длина:

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

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

2. Равенство.
Представьте «🌉» в строке, используя Unicode, \ud83c\udf09как показано ниже, и проверьте равенство.

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

Java не поддерживает UTF-32

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

3. Вы можете преобразовать символ Unicode в строку Java

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

4. String.substring () не учитывает дополнительные символы

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

Чтобы решить это, мы можем использовать String.offsetByCodePoints(int index, int codePointOffset)

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

5. Строка Итерация Unicode с BreakIterator
6. Сортировка строк с Unicode java.text.Collator
7. персонажа toUpperCase(), toLowerCase()методы не должны использоваться, вместо этого, используйте строку в верхний регистр и нижний регистр в конкретной местности.
8. Character.isLetter(char ch)не поддерживает, лучше использует Character.isLetter(int codePoint), для каждого methodName(char ch)метода в классе Character будет тип, methodName(int codePoint)который может обрабатывать дополнительные символы.
9. Укажите кодировку String.getBytes(), преобразуя из байтов в строку InputStreamReader,OutputStreamWriter

Ссылка:
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

Больше информации на примере image1 image2
Другие термины, которые стоит изучить: нормализация , BiDi

ДКБ
источник
2
Войдите специально, чтобы проголосовать за этот ответ (я имею в виду изменил окно с инкогнито на нормальное: P). Лучшее объяснение для новичка
N-JOY
1
Спасибо! Я рад, что это помогло, но автор оригинального сообщения заслуживает всяческих благодарностей.
ДКБ
Прекрасные примеры! Я вошел в систему, чтобы объявить это тоже :) И снова это заставило меня подумать (опять же), что я действительно не понимаю, почему Java поддерживает ИЗВЕСТНЫЕ ошибки в их коде. Я полностью уважаю, что они не хотят ломать существующий код, но давай ... сколько часов было потрачено на работу над этими ошибками? Если он сломан, исправь это, черт возьми!
Франц Д.
6

Суррогатные пары относятся к способу кодирования определенных символов в UTF-16, см. Http://en.wikipedia.org/wiki/UTF-16/UCS-2#Code_points_U.2B10000..U.2B10FFFF.

DFB
источник
11
«характер» - это такой загруженный термин.
Трийнко
1
В Unicode нет символов, но есть кодовые точки. Каждая кодовая точка может отображать от нуля до нескольких символов.
Николай Волынкин
6

Небольшое предисловие

  • Юникод представляет кодовые точки. Каждая кодовая точка может быть закодирована в 8-, 16- или 32-битных блоках в соответствии со стандартом Unicode.
  • До версии 3.1 в основном использовались 8-битное кодирование, известное как UTF-8, и 16-битное кодирование, известное как UCS-2 или «Универсальный набор символов, закодированный в 2 октета». UTF-8 кодирует точки Unicode как последовательность 1-байтовых блоков, в то время как UCS-2 всегда занимает 2 байта:

    A = 41 - один блок из 8 битов с UTF-8
    A = 0041 - один блок из 16 битов с UCS-2
    Ω = CE A9 - два блока из 8 битов с UTF-8
    Ω = 03A9 - один блок из 16 бит с UCS-2

проблема

Консорциум полагал, что 16 бит будет достаточно для охвата любого понятного человеку языка, что дает 2 ^ 16 = 65536 возможных значений кода. Это было верно для плоскости 0, также известной как BPM или базовая многоязычная плоскость, которая включает 55,445 из 65536 кодовых точек сегодня. BPM охватывает практически все человеческие языки в мире, включая китайско-японско-корейские символы (CJK).

Прошло время, и были добавлены новые азиатские наборы символов, китайские символы набрали более 70000 баллов. Теперь есть даже очки Emoji как часть стандартного 😺. Новые 16 "дополнительных" самолетов были добавлены. Помещения UCS-2 было недостаточно, чтобы покрыть что-то большее, чем Plane-0.

Решение Unicode

  1. Ограничить Unicode 17 самолетами × 65 536 символов на самолет = 1 114 112 максимальных точек.
  2. Представьте UTF-32, ранее известный как UCS-4, для хранения 32 битов для каждой кодовой точки и охвата всех плоскостей.
  3. Продолжайте использовать UTF-8 в качестве динамического кодирования, ограничьте UTF-8 максимум 4 байтами для каждой кодовой точки, то есть от 1 до 4 байтов на точку.
  4. Устаревший UCS-2
  5. Создайте UTF-16 на основе UCS-2. Сделайте UTF-16 динамическим, чтобы он занимал 2 байта или 4 байта на точку. Присвойте 1024 балла U + D800 – U + DBFF, называемых High Surrogates, UTF-16; назначить 1024 символа U + DC00-U + DFFF, называемых Low Surrogates, UTF-16.

    С этими изменениями BPM покрывается 1 блоком из 16 битов в UTF-16, тогда как все «дополнительные символы» покрываются суррогатными парами, представляющими 2 блока по 16 битов каждый, всего 1024x1024 = 1 048 576 точек.

    Высокий суррогат предшествует низкому суррогату . Любое отклонение от этого правила считается плохой кодировкой. Например, суррогат без пары неверен, низкий суррогат стоит перед старшим суррогатом неверен.

    𝄞, «MUSICAL SYMBOL G CLEF», кодируется в UTF-16 как пара суррогатов 0xD834 0xDD1E (2 на 2 байта),
    в UTF-8 как 0xF0 0x9D 0x84 0x9E (4 на 1 байт),
    в UTF-32 как 0x0001D11E (1 на 4 байта).

Текущая ситуация

  • Хотя в соответствии со стандартом суррогаты специально назначаются только для UTF-16, исторически некоторые приложения Windows и Java использовали точки UTF-8 и UCS-2, зарезервированные в настоящее время для суррогатного диапазона.
    Для поддержки устаревших приложений с некорректными кодировками UTF-8 / UTF-16 был создан новый стандарт WTF-8 , Wobbly Transformation Format. Он поддерживает произвольные суррогатные точки, такие как непарный суррогат или неправильная последовательность. Сегодня некоторые продукты не соответствуют стандарту и рассматривают UTF-8 как WTF-8.
  • Суррогатное решение открыло много проблем безопасности при преобразовании между различными кодировками, большинство из них были обработаны хорошо.

Многие исторические детали были скрыты, чтобы следовать теме ⚖.
Последний стандарт Unicode можно найти по адресу http://www.unicode.org/versions/latest.

Artru
источник
3

Суррогатная пара - это две «кодовые единицы» в UTF-16, которые составляют одну «кодовую точку». В документации Java говорится, что эти «кодовые точки» все еще будут действительны, а их «кодовые единицы» будут упорядочены правильно после обратного. Далее говорится, что две непарные единицы суррогатного кода могут быть обращены и образовать действительную суррогатную пару. Это означает, что если есть непарные кодовые блоки, то есть вероятность того, что обратная сторона реверса может не совпадать!

Однако обратите внимание, что в документации ничего не говорится о графемах - которые объединяют несколько кодовых точек. Это означает, что e и акцент, который идет вместе с ним, могут все еще быть переключены, таким образом, помещая акцент перед e. Это означает, что если перед е есть еще один гласный, он может получить ударение, которое было на е.

Хлоп!

Жерар ОНЕЙЛ
источник