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?