Java: Что такое символы, кодовые точки и суррогаты? В чем разница между ними?
Я пытаюсь разобраться в терминах "символ", "кодовая точка" и "суррогат", и хотя эти термины не ограничиваются только Java, мне хотелось бы получить объяснение, как они соотносятся с Java.
Я нашел некоторую информацию о различиях между символами и кодовыми точками: символы — это то, что отображается для пользователей, а кодовые точки — это значение, кодирующее конкретный символ. Однако я совсем не понимаю, что такое суррогаты. Что такое суррогаты и чем они отличаются от символов и кодовых точек? Правильно ли я понимаю определения символов и кодовых точек?
В другой теме на StackOverflow о том, как проходить по строке в виде массива символов, был комментарий, который побудил меня задать этот вопрос: "Обратите внимание, что эта техника дает вам символы, а не кодовые точки, что означает, что вы можете получить суррогаты." Я не совсем понял, и вместо того чтобы создавать длинную серию комментариев к вопросу, которому уже 5 лет, я подумал, что будет лучше задать уточняющий вопрос в новом посте.
1 ответ(ов)
Простыми словами:
Code unit— этоchar, который занимает 2 байта и закодирован вUTF-16. Каждыйcharне обязательно представляет собойреальный символ.Code point— это всегдареальный символ, который может содержать 1 или 2code 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 кодовых юнита по отдельности они не распознаются и отображаются как ? для каждого.
Инициализация ArrayList в одну строчку
Что значит 'synchronized'?
Почему нет ConcurrentHashSet, если есть ConcurrentHashMap?
Как объявить массив в одну строку?
Какие проблемы следует учитывать при переопределении equals и hashCode в Java?