Кодировка символов — это правило, которое ставит в соответствие символ (букву, цифру, знак) и его числовое представление, а затем последовательность байтов в памяти или файле. Разные кодировки бывают потому, что исторически по-разному отвечали на два вопроса: какие символы вообще существуют и сколько байтов тратить на каждый. В статье разберем главные семейства — ASCII, однобайтовые (Windows-1251, KOI8-R, ISO-8859), Unicode и его формы UTF-8, UTF-16, UTF-32, покажем на Python реальные байты для кириллицы и объясним, откуда берутся «кракозябры».
Содержание
- Два разных понятия: набор символов и кодировка
- ASCII: фундамент
- Однобайтовые кодировки: 256 символов и их беда
- Unicode: один набор символов для всех языков
- Формы кодирования Unicode: UTF-8, UTF-16, UTF-32
- Практика на Python: смотрим реальные байты
- Кракозябры: причина и как воспроизвести
- Выводы
- Где применяется / связь с практикой
- FAQ
Два разных понятия: набор символов и кодировка
Здесь легко запутаться, поэтому разведем термины сразу и одной цепочкой.
Набор символов (character set) — это таблица «символ -> число». Число называется кодовой точкой (code point). Пример: в Unicode букве «Я» назначена кодовая точка U+042F.
Кодировка (encoding) — это правило «кодовая точка -> байты». Пример: UTF-8 записывает U+042F двумя байтамиD0 AF.
Итого сквозная цепочка: символ «Я» -> кодовая точка U+042F -> байтыD0 AFв UTF-8.
Кодовая точка — это абстрактное число, а байт — то, что реально лежит на диске. В старых однобайтовых кодировках эти уровни совпадали (одна точка = один байт), поэтому разницу часто не замечали. В Unicode они разошлись, и путаница «Unicode = UTF-8» родом именно отсюда.
ASCII: фундамент
ASCII — это набор из 128 символов (кодовые точки 0-127), который помещается в 7 бит. В него входят латинские буквы, цифры, знаки препинания и управляющие символы. Кириллицы, умляутов и эмодзи в нем нет.
ASCII важен потому, что его первые 128 позиций легли в основу почти всех последующих кодировок: и однобайтовых, и UTF-8. Если текст состоит только из латиницы и цифр, он выглядит одинаково почти везде — совместимость с ASCII сохраняется.
Однобайтовые кодировки: 256 символов и их беда
Когда потребовались национальные алфавиты, свободный восьмой бит использовали для второй половины таблицы (позиции 128-255). Так появились однобайтовые кодировки: первые 128 символов — как в ASCII, а верхние 128 у каждой свои.
Для кириллицы распространились несколько несовместимых вариантов:
- Windows-1251 (cp1251) — основная кириллическая кодировка эпохи Windows.
- KOI8-R — кодировка, популярная в UNIX и ранней рунет-почте; буквы расставлены так, что при потере восьмого бита текст оставался читаемым транслитом.
- ISO-8859-5 — часть семейства ISO-8859, где ISO-8859-1 (Latin-1) покрывает Западную Европу, а -5 добавляет кириллицу.
- CP866 (DOS) — кодировка русской MS-DOS.
Общая беда однобайтовых кодировок: в 256 позиций помещается лишь один-два алфавита. Смешать русский, греческий и, скажем, японский в одном файле нельзя, а один и тот же байт 0xD0 в разных кодировках означает разные буквы. Это прямая причина «кракозябр».
Unicode: один набор символов для всех языков
Unicode — это единый набор символов, который назначает уникальную кодовую точку каждому знаку всех письменностей мира, а также эмодзи и техническим символам. Запись кодовой точки — U+ и шестнадцатеричное число: U+0041 (A), U+042F (Я), U+1F600 (эмодзи).
Важно: Unicode — это НЕ формат байтов. Он отвечает на вопрос «какие символы существуют и какой номер у каждого», но не говорит, как их хранить. За хранение отвечают формы кодирования — UTF-8, UTF-16, UTF-32. Их и путают с самим Unicode.
Пространство Unicode рассчитано на 1 114 112 кодовых точек (U+0000 — U+10FFFF); на 2026 год назначена лишь часть из них, стандарт регулярно пополняется. Точное число присвоенных символов зависит от версии стандарта.
Формы кодирования Unicode: UTF-8, UTF-16, UTF-32
Одну и ту же кодовую точку можно записать в байты по-разному. Отсюда три формы.
| Кодировка | Байтов на символ | Совместима с ASCII | Где применяется |
|---|---|---|---|
| ASCII | 1 (7 бит) | сама и есть основа | только латиница, цифры, управляющие |
| Windows-1251 | 1 | да (0-127) | legacy-файлы и БД с кириллицей |
| KOI8-R | 1 | да (0-127) | старая почта, UNIX-тексты |
| ISO-8859-5 | 1 | да (0-127) | часть семейства ISO-8859 |
| UTF-8 | 1-4 (переменная) | да, латиница = 1 байт | веб, файлы, API — формат по умолчанию |
| UTF-16 | 2 или 4 | нет | внутренняя строка в Java, C#, JavaScript, Windows API |
| UTF-32 | 4 (фиксированная) | нет | обработка, где нужен прямой доступ по индексу точки |
Коротко про различие форм:
- UTF-8 — переменная длина 1-4 байта. Латиница занимает 1 байт (как ASCII), кириллица — 2, многие иероглифы — 3, эмодзи — 4. Экономна для текстов с латиницей и совместима с ASCII, поэтому стала стандартом в вебе и файлах.
- UTF-16 — 1 или 2 «кодовые единицы» по 2 байта. Символы вне базовой плоскости (эмодзи и часть иероглифов) кодируются суррогатной парой (4 байта). Здесь появляется зависимость от порядка байтов (endianness), который помечается BOM. Используется внутри строк Java, C# и JavaScript.
- UTF-32 — всегда 4 байта на кодовую точку. Расточительна по памяти, зато номер символа лежит в байтах напрямую.
Кодовая точка vs кодовая единица vs графема. Кодовая точка — номер символа в Unicode (U+042F). Кодовая единица — минимальный блок конкретной формы (1 байт в UTF-8, 2 байта в UTF-16). Графема — то, что человек видит как один знак; иногда она собрана из нескольких кодовых точек (буква с диакритикой, составной эмодзи). Поэтому «число байтов», «число кодовых точек» и «число видимых символов» — три разные величины.
Практика на Python: смотрим реальные байты
Метод encode превращает строку (кодовые точки) в байты по выбранной кодировке, а decode делает обратное. Посмотрим байты кириллицы в UTF-8 — минимальный полный пример, который можно скопировать и запустить.
s = "Привет"
b = s.encode("utf-8") # строка -> байты
print(" ".join(f"{x:02X}" for x in b)) # шестнадцатеричные байты
print("байтов:", len(b), "символов:", len(s))
print("Я ->", hex(ord("Я"))) # кодовая точка буквы Я
print(b.decode("utf-8")) # байты -> строка обратно
Фактический вывод:
D0 9F D1 80 D0 B8 D0 B2 D0 B5 D1 82
байтов: 12 символов: 6
Я -> 0x42f
Привет
Видно главное: 6 символов кириллицы заняли 12 байтов — по 2 байта на букву в UTF-8, тогда как латинская буква заняла бы 1 байт. Кодовая точка «Я» равна U+042F, а в байтах UTF-8 это D0 AF (проверьте отдельным "Я".encode("utf-8")).
Кракозябры: причина и как воспроизвести
«Кракозябры» (mojibake) появляются, когда байты записаны в одной кодировке, а прочитаны как другая. Байты не «портятся» — их просто трактуют по чужой таблице. Покажем ошибку тройкой: неверное чтение -> результат -> исправление.
raw = "Привет".encode("utf-8") # байты D0 9F ... в UTF-8
# 1) неверно: читаем UTF-8 байты как Windows-1251
print(raw.decode("cp1251"))
Фактический вывод (типичные кракозябры):
Привет
Более грубая ошибка — прочитать те же байты как ASCII, где старших кодов нет:
raw.decode("ascii")
Фактический вывод — исключение:
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd0 in position 0: ordinal not in range(128)
Исправление одно и то же: читать теми же байтами и той же кодировкой, которой писали.
print(raw.decode("utf-8")) # -> Привет
Практический вывод: при чтении файла, ответа API или строки из БД всегда знайте и указывайте кодировку явно. Большинство современных источников — UTF-8, но legacy-выгрузки нередко приходят в Windows-1251.
Выводы
- Кодировка — это правило «символ -> число -> байты»; различать надо набор символов (таблица символ -> кодовая точка) и кодировку (кодовая точка -> байты).
- ASCII (128 символов, 7 бит) — общая основа; однобайтовые Windows-1251, KOI8-R, ISO-8859 добавляют по одному алфавиту в позиции 128-255 и потому взаимно несовместимы.
- Unicode — это набор кодовых точек для всех письменностей, а не формат байтов; байтами его записывают формы UTF-8, UTF-16, UTF-32.
- UTF-8 переменной длины (латиница 1 байт, кириллица 2, эмодзи 4) и совместима с ASCII — поэтому стала стандартом; кириллица в UTF-8 занимает 2 байта на букву.
- Кракозябры — это чтение байтов чужой кодировкой; лечится указанием правильной кодировки, а не «починкой» текста.
Где применяется / связь с практикой
Работа с кодировками — повседневная часть системного и низкоуровневого программирования: чтение файлов и сетевых буферов, обмен данными между сервисами, разбор протоколов, где важно точно знать, сколько байтов занимает символ и в каком порядке они идут. Ошибка на этом уровне превращается в кракозябры, битые логи и падения парсеров, поэтому понимание перехода «кодовая точка -> байты» ценят везде, где данные ходят в бинарном виде.
Освойте тему на практике
Глубже разобраться с представлением данных в памяти, буферами и работой с байтами на низком уровне помогает курс Системное программирование. Прежде чем записываться, можно бесплатно оценить формат и уровень на открытых уроках Otus — там разбирают практические задачи вживую.
FAQ
Чем UTF-8 отличается от Unicode?
Unicode назначает символам номера (кодовые точки), а UTF-8 — это одно из правил, как записать эти номера в байты. Unicode — набор символов, UTF-8 — форма кодирования.
Почему кириллица в UTF-8 занимает 2 байта, а латиница 1?
UTF-8 переменной длины: кодовые точки 0-127 (латиница, цифры) пишутся одним байтом ради совместимости с ASCII, а кириллица лежит выше и требует двух байтов.
Какую кодировку выбрать для нового файла или базы?
По умолчанию UTF-8: она совместима с ASCII, покрывает все языки и стала стандартом в вебе и файлах. Однобайтовые кодировки оставляют для чтения старых данных.



