0

Java: Что такое символы, кодовые точки и суррогаты? В чем разница между ними?

11

Я пытаюсь разобраться в терминах "символ", "кодовая точка" и "суррогат", и хотя эти термины не ограничиваются только Java, мне хотелось бы получить объяснение, как они соотносятся с Java.

Я нашел некоторую информацию о различиях между символами и кодовыми точками: символы — это то, что отображается для пользователей, а кодовые точки — это значение, кодирующее конкретный символ. Однако я совсем не понимаю, что такое суррогаты. Что такое суррогаты и чем они отличаются от символов и кодовых точек? Правильно ли я понимаю определения символов и кодовых точек?

В другой теме на StackOverflow о том, как проходить по строке в виде массива символов, был комментарий, который побудил меня задать этот вопрос: "Обратите внимание, что эта техника дает вам символы, а не кодовые точки, что означает, что вы можете получить суррогаты." Я не совсем понял, и вместо того чтобы создавать длинную серию комментариев к вопросу, которому уже 5 лет, я подумал, что будет лучше задать уточняющий вопрос в новом посте.

1 ответ(ов)

0

Простыми словами:

  • Code unit — это char, который занимает 2 байта и закодирован в UTF-16. Каждый char не обязательно представляет собой реальный символ.
  • Code point — это всегда реальный символ, который может содержать 1 или 2 code unit. Рассматривайте его как int, который может занимать 4 байта.

Пусть код (тестовый случай) говорит сам за себя:

(нужен Java 9+, из-за методов codePoints() и chars() в классе String)

@Test
public void test() {
    String s = "Hi, 你好, おはよう, α-Ω\uD834\uDD1E"; // последний реальный символ — "𝄞", он занимает 2 code unit,
    assertEquals(s.length(), s.toCharArray().length); // length() основан на char (т.е. кодовом юните), а не на кодовой точке,

    System.out.printf("входная строка:\t\"%s\"%n%n", s);

    System.out.println("------ в виде кодовой точки (т.е. реального символа) ------");
    // кодовые точки,
    s.codePoints().forEach(cp -> System.out.println(Character.toChars(cp)));
    assertEquals(s.codePoints().count(), s.length() - 1); // последний читаемый символ занимает 2 кодовых юнита,
    assertEquals(s.codePoints().count(), s.codePointCount(0, s.length())); // есть метод codePointCount() в классе String для получения количества кодовых точек в заданном диапазоне символов,

    System.out.println("\n------ в виде char (т.е. кодового юнита) ------");
    // символы (т.е. кодовые юниты),
    s.chars().forEach(c -> System.out.println(Character.toChars(c)));
    assertEquals(s.chars().count(), s.length()); // длина строки равна количеству кодовых юнитов, а не кодовых точек,
}

Вывод:

входная строка:   "Hi, 你好, おはよう, α-Ω𝄞"

------ в виде кодовой точки (т.е. реального символа) ------
H
i
,
 
你
好
,
 
お
は
よ
う
,
 
α
-
Ω
𝄞

------ в виде char (т.е. кодового юнита) ------
H
i
,
 
你
好
,
 
お
は
よ
う
,
 
α
-
Ω
?
?

Последний реальный символ — 𝄞, он занимает 2 кодовых юнита \uD834\uDD1E и является одной кодовой точкой. При попытке напечатать 2 кодовых юнита по отдельности они не распознаются и отображаются как ? для каждого.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь