Символьные кодировки: ASCII, Windows-1251, KOI8-R и Unicode

Символьные кодировки: ASCII, Windows-1251, KOI8-R и Unicode Полезное

Символьная кодировка — это правило, по которому кодовой точке символа сопоставляется последовательность байт для хранения и передачи текста. Кодовую точку задает набор символов — перечень знаков с присвоенными им номерами. Одному и тому же набору символов могут соответствовать разные кодировки: кириллица в Windows-1251 занимает 1 байт на символ, а в UTF-8 — обычно 2 байта. Разберу основные символьные кодировки от ASCII до Unicode, покажу их различия и объясню, почему из-за путаницы между ними на экране появляются кракозябры.

Набор символов, кодовая точка, кодировка: не путать

Три термина легко смешать, а они означают разное:

Термин Что это Пример
Набор символов (character set) Перечень знаков с присвоенными им номерами Кириллица, латиница, ASCII-набор
Кодовая точка (code point) Номер конкретного символа в наборе U+042F — точка для буквы «Я»
Кодировка (encoding) Правило перевода кодовой точки в байты UTF-8, Windows-1251, KOI8-R
Кодовая единица (code unit) Минимальный блок байт, из которых кодировка собирает точку 1 байт в UTF-8, 2 байта в UTF-16

Сквозная цепочка на одном символе: буква «Я» — это кодовая точка U+042F в Unicode. В кодировке UTF-8 она превращается в 2 байта, в Windows-1251 — в 1 байт, а сами байты в этих кодировках разные:

ch = "Я"
print(ch.encode("utf-8"))       # b'\xd0\xaf' - 2 байта
print(ch.encode("windows-1251"))  # b'\xdf' - 1 байт

Вывод: b'\xd0\xaf' и b'\xdf' — это два разных байтовых представления одной и той же кодовой точки. Если прочитать вторые байты как UTF-8, получится не «Я», а ошибка или другой знак — в этом суть кракозябр (разберу ниже).

ASCII: база для всего остального

ASCII (American Standard Code for Information Interchange) — стандарт кодирования, принятый в 1963 году Американской ассоциацией по стандартам (ASA, позже переименована в ANSI). Изначально 7-битный: под него отводится 128 кодовых точек (0-127).

В ASCII входят:
— управляющие символы (0-31 и 127) — для оборудования, а не для печати;
— цифры и знаки препинания;
— заглавные и строчные буквы латинского алфавита.

Национальных алфавитов, включая кириллицу, в базовом ASCII нет — для них позже появились расширения и отдельные кодировки. Важный практический факт: первые 128 кодовых точек Unicode и первые 128 байт-значений UTF-8 совпадают побайтно с ASCII — поэтому текст, целиком состоящий из латиницы и цифр, одинаково читается и в ASCII, и в UTF-8.

Расширенные однобайтовые кодировки: CP866, KOI8-R, Windows-1251

Когда байт стал 8-битным, для второй половины таблицы (128-255) появились десятки региональных кодировок. Для кириллицы прижились три:

Кодировка Байт на символ Где применялась Статус сейчас
CP866 1 MS-DOS, консоль Windows (legacy) Практически не встречается вне старого ПО
KOI8-R 1 Почта и Unix-системы 1990-х Изредка в старых письмах/логах
Windows-1251 1 Кириллические версии Windows, веб-страницы 1990-2000-х Встречается в старых сайтах и файлах, вытеснена UTF-8

Все три — упрощенная модель: они кодируют только один язык (русский плюс несколько родственных) и работают, пока и автор, и читатель текста используют одну и ту же таблицу. Как только кодировки расходятся, байты остаются те же, а символы, в которые их разворачивает получатель, — другие.

В KOI8-R кириллица расположена не по алфавиту: буквы стоят на местах, близких по начертанию к латинским (историческое решение для совместимости с транслитерацией по 7-битным каналам). В CP866 и Windows-1251 порядок ближе к алфавитному, но раскладки все равно разные — отсюда несовместимость байт напрямую между всеми тремя.

Откуда берутся кракозябры

Кракозябры — это не порча данных, а рассинхронизация: байты не менялись, но программа читает их не той таблицей, которой они были записаны. Пример на Python — записываю строку в Windows-1251, а читаю как KOI8-R:

original = "Привет"
raw_bytes = original.encode("windows-1251")
broken = raw_bytes.decode("koi8-r")
print(broken)

Неверный результат: оПХБЕР (проверено запуском кода выше). Исправление — декодировать теми же байтами, но правильной кодировкой:

fixed = raw_bytes.decode("windows-1251")
print(fixed)  # Привет

Итог: если текст превратился в кракозябры, сами байты почти всегда целы. Задача — определить, в какой кодировке они были записаны, и раскодировать заново той же кодировкой. Автоматическое определение кодировки без явного указания (библиотеки вроде chardet) работает по статистической эвристике и не дает 100% гарантии, особенно на коротких текстах.

Unicode: UCS и семейство UTF

Unicode — стандарт, который присваивает уникальную кодовую точку каждому символу вне зависимости от языка, включая иероглифы, математические знаки и нотные символы. Разработку с 1991 года ведет некоммерческий Unicode Consortium. Количество назначенных кодовых точек растет с каждой версией стандарта — точное число на конкретный момент лучше сверять с сайтом консорциума, а не считать постоянной величиной.

Здесь важно развести два слоя, которые в обиходе называют одним словом «Юникод»:
— UCS (universal character set) — сама таблица кодовых точек, куда каждому символу присвоен номер вида U+XXXX;
— UTF (Unicode Transformation Format) — семейство кодировок, которые превращают эти номера в реальные байты для хранения и передачи.

Кодовая точка одна, а способов ее записать байтами — несколько, и они дают разный результат:

Кодировка Байт на символ Особенность Типичное применение
UTF-8 1-4, переменная длина Первые 128 точек совпадают с ASCII; нет проблемы порядка байт Веб, файлы, API — стандарт по умолчанию
UTF-16 2 или 4 (суррогатные пары для редких символов) Зависит от порядка байт (little/big-endian) Внутреннее представление строк в Windows API, Java, JavaScript
UTF-32 4, фиксированная длина Простая индексация символов, но самый большой объем Внутренние вычисления, редко для хранения/передачи

Пример вывода для одной и той же буквы «Я» (U+042F) в трех кодировках, с проговоренным результатом:

ch = "Я"
print(ch.encode("utf-8"))     # b'\xd0\xaf'          - 2 байта
print(ch.encode("utf-16-le")) # b'/\x04'              - 2 байта, little-endian
print(ch.encode("utf-32-le")) # b'/\x04\x00\x00'      - 4 байта, дополнены нулями

Порядок байт в UTF-16/UTF-32 зависит от платформы, поэтому в коде, где это важно, стоит явно проверять или задавать endianness, а не полагаться на умолчание:

import sys
print(sys.byteorder)  # 'little' на большинстве современных ПК, но не гарантированно

Это упрощенная демонстрация принципа: реальные текстовые процессоры дополнительно учитывают BOM (byte order mark) и обработку суррогатных пар для символов за пределами базовой многоязыковой плоскости — в статью эти детали не помещаются, но важно знать об их существовании при работе с файлами в UTF-16.

Как выбрать кодировку на практике

Короткое правило на 2026 год: для новых текстовых файлов, веб-страниц и API по умолчанию берите UTF-8 — он совместим с ASCII, не имеет проблемы порядка байт и поддерживает любой язык одной кодировкой. Однобайтовые кодировки (Windows-1251, KOI8-R, CP866) имеет смысл учитывать только при работе с legacy-файлами и старыми системами, где их меняют осознанно, а не потому что «так исторически сложилось».

Выводы

  • Кодовая точка (номер символа), набор символов (перечень точек) и кодировка (правило перевода в байты) — три разных понятия, их не стоит называть одним словом «кодировка символов».
  • ASCII — основа: 128 точек, первые 7 бит совпадают с началом любой современной кодировки на основе Unicode.
  • CP866, KOI8-R и Windows-1251 — однобайтовые кодировки кириллицы; они несовместимы между собой на уровне байт, отсюда кракозябры при путанице.
  • Кракозябры чинятся не переписыванием текста, а декодированием тех же байт правильной кодировкой.
  • UTF-8, UTF-16 и UTF-32 — три способа записать один и тот же Unicode-код байтами; для новых проектов по умолчанию берут UTF-8.

Где применяется / связь с практикой

Освойте тему на практике

Работа с кодировками — обязательный навык при обработке текста, парсинге файлов и веб-разработке на Python: чтение файла в неправильной кодировке или неверная сериализация JSON — частая причина UnicodeDecodeError в проде. Разбор строк, байтовых объектов и метода encode/decode входит в базовый курс Python — там эти темы разбираются на практических задачах. Потренироваться на живых примерах и задать вопросы преподавателю можно на открытых уроках Otus.

Смежные темы: Типы данных в языках программирования.

FAQ

Какая кодировка используется в интернете по умолчанию?
UTF-8 — рекомендация W3C/WHATWG и фактический стандарт для новых веб-страниц и API; именно ее стоит указывать в <meta charset> и заголовках ответа, если нет причины использовать другую.

Можно ли определить кодировку файла автоматически, если нет BOM?
Только эвристически: библиотеки вроде chardet анализируют статистику байт и предполагают наиболее вероятную кодировку, но не гарантируют результат, особенно на коротких или смешанных текстах. Надежнее знать кодировку заранее из источника файла.

Почему при открытии старого файла в текстовом редакторе вместо кириллицы видны случайные символы?
Это тот же случай рассинхронизации: файл записан в одной однобайтовой кодировке (например, CP866), а редактор по умолчанию открывает его в другой (например, Windows-1251 или UTF-8) — нужно вручную указать редактору исходную кодировку.

OTUS Журнал