«Привет» → Привет. Каждый, кто хоть раз открывал русский текст в неправильной программе, видел этот кошмар. Но что здесь произошло? Спойлер: никто ничего не испортил — просто байты одного алфавита прочитали по правилам другого.
Внутри компьютера нет букв. Есть только числа. Буква «А» — это число 65, буква «Я» — число 1071, а любимый всеми эмодзи «😀» — вообще 128512.
Каждая буква, цифра и знак — это просто кодовая точка.
Кодировка — это правило, как превратить номер символа в байты и обратно. Есть кодировки однобайтовые (один символ = один байт) и многобайтовые (один символ = от 1 до 4 байт). Именно из-за этого возникает 90% всей путаницы.
Двигаемся дальше — сейчас вы увидите, как одна и та же фраза выглядит в разных кодировках байт за байтом.
Каждый байт — это 8 бит, то есть число от 0 до 255. В 16-ричном виде это два символа: D0, 9F и так далее. Смотрите, как одна и та же фраза превращается в совершенно разные байты в зависимости от выбранной кодировки.
Вот и разгадка. Кто-то записал текст в одной кодировке, а прочитал в другой. Например, страница была в UTF-8, но браузер по ошибке решил, что она в CP1251. Байты правильные — а буквы из них получаются чужие. Выберите пару кодировок и посмотрите на результат.
В UTF-8 разные символы занимают разное количество байтов. Латиница — 1 байт, кириллица — 2, а вот эмодзи — целых 4. Из-за этого счётчик символов в приложениях иногда врёт: «😀» может считаться как 2, 4 или 7 символов — зависит от того, что именно считать.
Именно поэтому «👨👩👧» может влезть в твит с лимитом 140 символов как 1 «символ», а может съесть сразу 5 — зависит от того, что именно считать: сколько байтов, сколько кодовых точек или сколько «видимых» глифов.
Слово «символ» в программировании означает три совершенно разные вещи, и путаница между ними — источник половины проблем с кодировками. Давайте разложим по полочкам.
Что реально лежит в файле
Физическая единица. Восемь бит. Именно байты летят по сети и хранятся на диске. Одна буква может занимать 1, 2, 3 или 4 байта — зависит от кодировки и символа.
D0 90
— 2 байта в UTF-8
Номер символа в Unicode
Абстрактное число. У каждого символа есть свой номер: «A» — это 65, «Я» — 1071, «😀» — 128512. Записывается как U+XXXX. Одна кодовая точка — один «настоящий» символ.
U+0410
— один символ
Как символ выглядит на экране
Картинка. То, что вы видите. Одна и та же кодовая точка может рисоваться по-разному в разных шрифтах — это и есть разные глифы. Более того, один глиф может склеиваться из нескольких кодовых точек.
А А А
— 3 глифа, 1 кодовая точка
Представьте, что вы отправляете письмо. Байты — это бумажный конверт и штампы: физическая вещь, которая едет по почте. Кодовая точка — это смысл текста в письме, то, что вы хотели сказать. Глиф — это то, как получатель решит переписать ваш текст: своим почерком, печатными буквами или каллиграфически.
Конверт может быть длинный или короткий, смысл всегда один, а как получатель это перепишет — зависит от него, не от вас.
Один и тот же текст можно посчитать тремя способами — и получить три разных ответа. Вот почему счётчики символов в разных приложениях показывают разные числа.
| Текст | Байт (UTF-8) | Кодовых точек | Глифов |
|---|---|---|---|
A |
1 | 1 | 1 |
А (кириллица) |
2 | 1 | 1 |
😀 |
4 | 1 | 1 |
👨👩👧 (семья) |
18 | 5 | 1 |
🇷🇺 (флаг) |
8 | 2 | 1 |
Привет |
12 | 6 | 6 |
💡 Забавный факт: «👨👩👧» в твите считается как 1 символ, а в некоторых SMS — как 5. Никто не обманывает — просто все считают по-своему.
Буква «Ё» — единственная в русском алфавите, которая не попала в «основную» раскладку клавиатуры и часто заменяется на «Е». Из-за этого она до сих пор приносит программистам головную боль:
В CP1251 (старая кодировка Windows) буква «Ё» стоит не рядом с «Е» в таблице — её код 0xA8, а «Е» — 0xC5. В KOI8-R — снова другое место. В Unicode — своя кодовая точка U+0401.
В итоге при конвертации между старыми кодировками именно «Ё» чаще всего превращается в «?» или «�». Хорошо, что UTF-8 объединила весь мир — но рефлекс «написать Е вместо Ё» остался.
Если вы работаете с текстом, базами данных или сайтами — эти правила избавят от 90% проблем с кодировками.
Это стандарт современного веба. Другие кодировки — только для совместимости с legacy-системами.
В HTML — <meta charset="utf-8">. В базах данных — CHARACTER SET utf8mb4. Не полагайтесь на «а вдруг угадает».
Данные, скорее всего, целы. Просто перекодируйте их из одной кодировки в другую — обычно туда и обратно.
Для латиницы, цифр и базовых знаков UTF-8 совпадает с ASCII — это сделано специально. Для всего остального — нет.
«Символ» может быть байтом, кодовой точкой или глифом. Если важен подсчёт — уточняйте, что именно считаете.
В Linux команда file -i filename покажет кодировку файла. Многие кодировки имеют BOM — невидимый маркер в начале файла.