Чем Unicode отличается от UTF-8?
- Что такое кодировка?
- Почему длина строки с эмодзи бывает больше, чем видно на экране?
- Чем UTF-8 отличается от UTF-16?
Unicode — таблица: каждому символу номер (кодовую точку), U+0416 — «Ж». UTF-8 — способ записать эти номера байтами.
- UTF-8: от 1 до 4 байт на символ, ASCII — 1 байт, кириллица — 2, эмодзи — 4
- UTF-16: 2 или 4 байта (суррогатные пары) — внутри Java, JavaScript, Windows
- «Длина строки» бывает в байтах, единицах кодировки, кодовых точках и видимых символах
А ещё Кракозябры — текст прочитали не в той кодировке, в которой записали
Подробно
Компьютер хранит байты, текст — договорённость, как их читать.
Unicode — единый каталог символов всех письменностей: каждому присвоен номер, кодовая точка, от U+0000 до U+10FFFF. Это ещё не байты.
Кодировка превращает номера в байты:
- UTF-8 — переменная длина: U+0000…U+007F (ASCII) — 1 байт, дальше 2, 3 или 4. Старшие биты первого байта говорят длину последовательности, а продолжения начинаются с
10, поэтому по любому месту в потоке можно найти начало символа. Совместима с ASCII, не зависит от порядка байтов — стандарт для файлов, сети и веба; - UTF-16 — единицы по 2 байта; символы за пределами базовой плоскости (эмодзи, редкие иероглифы) — парой единиц, суррогатной парой. Так строки устроены внутри Java, JavaScript, C#, Windows API;
- UTF-32 — всегда 4 байта, просто, но расточительно;
- однобайтовые — cp1251, KOI8-R: 256 символов, только одна письменность.
Что такое «длина» — частый вопрос с подвохом:
- байты —
len("Ж".encode())= 2; - единицы UTF-16 —
"😀".length()в Java = 2; - кодовые точки —
len("😀")в Python = 1; - графемы, видимые символы — флаг 🇷🇺 это две кодовые точки, а «семья» эмодзи — до семи, соединённых невидимым ZWJ. «й» можно записать одной точкой или «и» плюс комбинирующий знак — и строки не будут равны без нормализации (NFC).
На практике: всегда явно указывать кодировку при чтении файлов и в HTTP (Content-Type: text/html; charset=utf-8), в MySQL выбирать utf8mb4 (старый utf8 хранит максимум 3 байта и теряет эмодзи), обрезать строки по графемам, а не по байтам.
Пример
s = "Ж😀"
len(s) # 2 кодовые точки
s.encode("utf-8") # b'\xd0\x96\xf0\x9f\x98\x80' — 2 + 4 байта
len(s.encode("utf-16-le")) # 6 — «Ж» 2 байта, эмодзи суррогатной парой 4
"Привет".encode("utf-8").decode("cp1251") # 'Привет' — кракозябрыЧто хотят услышать
- Разделение: таблица символов против способа кодирования в байты.
- Переменная длина UTF-8 и совместимость с ASCII.
- UTF-16 и суррогатные пары — почему length у эмодзи 2.
- Разные смыслы длины строки и явная кодировка при вводе-выводе.
Частые ошибки
- «Unicode — это кодировка в 2 байта».
- Резать строку по байтам и получать половину символа.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✓Unicode — номера символов, UTF-8 — запись номеров байтами от 1 до 4 байт, кириллица занимает 2
- ✗UTF-8 — кодировка, в которой каждый символ занимает ровно 2 байта, а Unicode — другая кодировка с 4 байтами на символ
- ✗Unicode и UTF-8 — одно и то же
- ✗Длина строки в байтах всегда равна числу символов
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.