Кодировка символов: от ASCII до UTF-8
Почему текст иногда превращается в вопросительные знаки и странные символы. Практическое руководство по кодировкам.
Открываешь файл и видишь ü вместо u. Или база данных возвращает ???? там, где должно быть имя. Или приходит письмо с =?UTF-8?B? разбросанным по теме.
Добро пожаловать в мир проблем с кодировкой символов.
Краткая история
Компьютеры хранят числа, а не буквы. Поэтому кто-то должен был решить, какое число означает какую букву. В 1960-х ASCII присвоил номера 0-127 английским буквам, цифрам и основным символам. Буква "A" — это 65. Пробел — 32. Просто.
Но ASCII покрывает только 128 символов. Для английского хватает. Для немецких умляутов, японских кандзи, арабской письменности или тысяч других символов, которые люди реально используют, — нет.
Хаос до Unicode
Разные регионы изобретали свои кодировки. Западная Европа получила ISO-8859-1. Япония — Shift-JIS. Россия — KOI8-R. Китай — GB2312. Каждая работала нормально внутри своей экосистемы. Как только их смешивали — всё ломалось.
Файл, сохранённый в одной кодировке и открытый в другой, даёт кракозябры — ту самую кашу из неправильных символов, которую ты наверняка видел. cafe превращается в café, когда файл UTF-8 читается как ISO-8859-1.
Unicode решил проблему сопоставления
Unicode дал каждому символу уникальный номер (называемый «кодовой точкой»). Латинская A — это U+0041. Снеговик — U+2603. Каждый эмодзи, каждая письменность, каждый математический символ получает свою кодовую точку. Более 150 000 символов и число растёт.
Но Unicode — это только сопоставление. Он не говорит, как хранить эти числа в виде байтов. Это задача кодировки.
UTF-8: кодировка-победитель
UTF-8 — это способ, которым большая часть интернета хранит текст Unicode. Ключевой приём: используется переменное количество байтов на символ.
- Символы ASCII (английские буквы, цифры): по 1 байту
- Европейские символы с диакритиками: по 2 байта
- Азиатские символы (CJK): по 3 байта
- Эмодзи и редкие символы: по 4 байта
Это значит, что английский текст в UTF-8 идентичен ASCII. Старые системы не ломаются. Но при этом можно представить любой символ в мире.
Сейчас более 98% сайтов используют UTF-8. Война кодировок окончена, и UTF-8 победил.
UTF-8 vs UTF-16 vs UTF-32
UTF-8: Переменная ширина (1-4 байта). Эффективен для текста на английском. Веб-стандарт.
UTF-16: Переменная ширина (2 или 4 байта). Используется внутри JavaScript, Java и Windows. Каждый символ — минимум 2 байта, поэтому менее эффективен для ASCII-текста.
UTF-32: Фиксированная ширина (4 байта на символ). Просто, но расточительно. Редко используется для хранения или передачи.
string.length в JavaScript считает кодовые единицы UTF-16, а не символы. Поэтому "😀".length возвращает 2, а не 1.
Когда кодировка ломается
Чтение файла в неправильной кодировке. Байты в порядке, но интерпретируются неправильно. Решение: указать правильную кодировку при открытии.
Несоответствие кодировки базы данных. Приложение отправляет UTF-8, а столбец базы данных настроен на latin1. Символы за пределами ASCII портятся. Решение: настройте базу на utf8mb4 (не просто utf8 в MySQL, который обрабатывает только 3-байтовые символы).
Отсутствие заголовка charset в HTTP. Если сервер не отправляет Content-Type: text/html; charset=utf-8, браузерам приходится угадывать. Иногда они угадывают неправильно.
Практические советы
Всегда используй UTF-8. Если нет очень конкретной причины поступить иначе, UTF-8 — правильный выбор для всего.
Объявляй кодировку явно. В HTML: <meta charset="utf-8">. В HTTP: Content-Type: text/html; charset=utf-8. Не заставляй системы гадать.
Будь осторожен с длиной строк. В JavaScript подсчёт символов усложняется с эмодзи и комбинирующими символами. Используй Array.from(str).length или API Intl.Segmenter для точного подсчёта.
Следи за BOM. Метка порядка байтов (U+FEFF) иногда появляется в начале файлов UTF-8. Она невидима, но может сломать парсеры. Некоторые редакторы добавляют её тихо.
Кодировка символов — не самая захватывающая тема, но понимание сэкономит часы отладки. Используй UTF-8 везде, объявляй явно, и когда увидишь кракозябры, будешь точно знать, где искать.