Що таке "сурогатна пара" на Java?


149

Я читав документацію для StringBuffer, зокрема, методу зворотного () . У цій документації щось згадується про сурогатних пар . Що таке сурогатна пара в цьому контексті? А що таке низькі та високі сурогати?


3
Це термінологія UTF-16, пояснена тут: download.oracle.com/javase/6/docs/api/java/lang/…
wkl

1
Цей метод є помилковим: він повинен реверсувати повні символи ᴀᴋᴀ кодові точки - не окремі їх частини, ᴀᴋᴀ кодові одиниці. Помилка в тому, що цей конкретний застарілий метод працює лише на окремих одиницях char, а не на кодових точках. Це те, що ви хочете String скласти, а не лише одиниці char. Занадто погана Java не дозволяє використовувати OO, щоб виправити це, але і Stringклас, і StringBufferкласи finalвилучені. Скажіть, це не евфемізм для вбитих? :)
tchrist

2
@tchrist Документація (та джерело) говорить, що вона робить зворотну як рядок кодових точок. (Імовірно, 1.0.2 цього не робив, і ти ніколи не отримаєш такої зміни поведінки в наші дні.)
Том Хоутін - таклін

Відповіді:


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. В той час ця конструкція мала сенс, оскільки всі символи Unicode мали значення менше 65,535 (0xFFFF) і могли бути представлені в 16 бітах. Однак пізніше Unicode збільшив максимальне значення до 1,114,111 (0x10FFFF). Оскільки 16-бітні значення були занадто малі, щоб представляти всі символи Unicode у версії 3.1 Unicode, для схеми кодування UTF-32 були прийняті 32-бітні значення - звані кодові точки. Але 16-бітові значення є кращими перед 32-бітовими значеннями для ефективного використання пам'яті, тому Unicode представив нову конструкцію для подальшого використання 16-бітних значень. Ця конструкція, прийнята в схемі кодування UTF-16, призначає 1,024 значення 16-бітовим високим сурогатам (у діапазоні U + D800 до U + DBFF) та іншим 1024 значенням 16-бітовим низьким сурогатам (у діапазоні U + DC00) до U + DFFF).


7
Мені це подобається більше, ніж прийнята відповідь, оскільки це пояснює, як Unicode 3.1 зарезервував 1024 + 1024 (високий + низький) значення з вихідних 65535, щоб отримати 1024 * 1024 нових значень, без додаткових вимог, які парсери починають на початку рядок.
Ерік Херст

1
Мені не подобається, що ця відповідь за те, що UTF-16 є найбільш ефективним для пам'яті кодуванням Unicode. UTF-8 існує, і більшість тексту не відображається як два байти. UTF-16 використовується в основному сьогодні, тому що Microsoft вибирала його до того, як UTF-32 була справою, а не ефективністю пам'яті. Про те, що UTF-16 дійсно хочеться, це те, коли ти робиш багато файлів в Windows, і тому багато читаєш і пишеш. В іншому випадку UTF-32 для високої швидкості (з / п постійні компенсації) або UTF-8 для низької пам’яті (b / c мінімум 1 байт)
фонд Моніки

23

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


6
Уточнення. Рядок повинен бути перетворений на "істинні" символи (також "графіки" або "текстові елементи"). Однією кодовою точкою "символів" можуть бути один або два фрагменти "char" (сурогатна пара), а графема може бути однією або декількома з цих кодових точок (тобто базовий код символів плюс один або більше комбінуючих символьних кодів, кожен з яких може бути один або два 16-бітні куски або "символи" довгими). Таким чином, одна графема може бути трьома комбінуючими символами кожні два "символи", що становить 6 "символів". Усі 6 символів повинні зберігатися разом, в порядку (тобто не перевернутись), коли перевертається весь рядок символів.
Трайнко

4
Отже, тип даних "char" є досить оманливим. "персонаж" - це вільний термін. Тип "char" насправді є лише розміром фрагмента UTF16, і ми називаємо його символом через відносну рідкість сурогатних пар, що виникають (тобто зазвичай це являє собою цілу кодову точку символу), тому "символ" дійсно відноситься до однієї кодової точки унікоду , але тоді за допомогою символів, що поєднуються, ви можете мати послідовність символів, які відображаються як єдиний "елемент / символ / графема / текст". Це не ракетна наука; поняття прості, але мова заплутана.
Трайнко

На час розробки Java, Unicode знаходився в зародковому стані. Ява існував близько 5 років, перш ніж Unicode отримав сурогатні пари, тож 16-розрядний char досить добре підходив на той час. Тепер вам набагато краще використовувати UTF-8 та UTF-32, ніж UTF-16.
Джонатан Болдуін

23

Додавання додаткової інформації до вищезазначених відповідей з цієї публікації.

Тестований на Java-12, повинен працювати у всіх версіях Java вище 5.

Як згадувалося тут: https://stackoverflow.com/a/47505451/2987755 ,
залежно від того , який символ (Unicode вище U + FFFF) представлений у вигляді сурогатної пари, яка Java зберігає як пару знакових знаків, тобто єдиний 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. Рівність:
представляйте "🌉" для String за допомогою Unicode, \ud83c\udf09як показано нижче, і перевірте рівність.

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

Java не підтримує UTF-32

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

3. Можна перетворити символ Unicode в Java String

"🌉".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)методу в класі символів буде тип, 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/supplementar-142654.html

Більше інформації про приклад image1 image2
Інші терміни, які варто вивчити: Нормалізація , BiDi


2
підписався спеціально, щоб проголосувати за цю відповідь (я маю на увазі змінив вікно з анонімного на звичайне: P). Найкраще пояснення для noob
N-JOY

1
Дякую !, Я радий, що це допомогло, але автор оригіналу публікації заслуговує всієї вдячності.
dkb

Чудові приклади! Я ввійшов у систему, щоб підтвердити це також :) І знову, це змусило мене думати (знову), що я дійсно не розумію, чому Java зберігає ЗНАННІ помилки у своєму коді. Я повністю поважаю, що вони не хочуть порушувати існуючий код, але давай ... скільки годин було втрачено на роботу над цими помилками? Якщо він зламаний, виправте, чорт!
Франц Д.

6

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


11
"символ" - це такий завантажений термін.
Трайнко

1
У Unicode немає символів, але є кодові точки. Кожна точка коду може відображати нуль до декількох символів.
Нік Волинкін

6

Невелика передмова

  • Unicode представляє кодові точки. Кожна точка коду може бути закодована в 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 або Basic Multilingual Plane, що включає 55,445 з 65536 кодових пунктів сьогодні. BPM охоплює майже кожну людську мову у світі, включаючи китайсько-японсько-корейську символіку (CJK).

Минув час і додавались нові азіатські набори символів, китайські символи забирали більше 70 000 очок. Тепер є навіть точки 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, звані Високі сурогати, UTF-16; присвоїти 1024 символу U + DC00 – U + DFFF, звані низькими сурогатами, UTF-16.

    При цих змінах BPM покривається 1 блоком з 16 біт в UTF-16, тоді як усі "Додаткові символи" покриті сурогатними парами, що представляють 2 блоки по 16 біт кожен, що становить 1024x1024 = 1 048 576 балів.

    Високий сурогат передує низькому сурогату . Будь-яке відхилення від цього правила вважається поганим кодуванням. Наприклад, сурогат без пари є неправильним, низький сурогат стоїть перед високим сурогатом неправильним.

    𝄞 "МУЗИЧНА СИМВОЛ 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


3

Сурогатна пара - це два "кодові одиниці" в UTF-16, які складають одну "кодову точку". Документація Java вказує, що ці "кодові точки" все ще будуть дійсними, а їхні "кодові одиниці" впорядковані правильно після зворотного. Далі зазначено, що дві парні кодові сурогатні одиниці коду можуть бути змінені та утворювати дійсну сурогатну пару. Що означає, що якщо є непарні парні кодові одиниці, то є ймовірність, що реверс зворотного може не бути однаковим!

Зверніть увагу, однак, документація нічого не говорить про Graphemes - це декілька кодових точок разом. Що означає e і наголос, що йде разом з ним, може все-таки перемикатися, таким чином, ставлячи наголос перед e. Що означає, якщо перед e є інший голосний, він може отримати наголос, який був на e.

Yikes!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.