Кодирование информации: биты, байты и кодировки текста

Кодирование информации: биты, байты и кодировки текста Полезное

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

Дальше разберем, как компьютер сводит любые данные к нулям и единицам, что такое кодировки текста ASCII, Unicode и UTF-8, проследим сквозную цепочку «символ -> код-поинт -> байты» на одном примере и явно разведем три понятия, которые постоянно путают: кодирование, шифрование и сжатие.

Мини-словарь: три пары, которые путают

Чтобы примеры дальше не казались противоречивыми, разведем термины сразу.

  • Кодирование — смена формы записи по открытым правилам (текст -> байты, число -> двоичный код). Обратимо без секрета.
  • Шифрование — смена формы записи так, чтобы прочитать мог только владелец ключа. Обратимо только с ключом.
  • Сжатие — уменьшение объема данных. Обратимо без секрета (если сжатие без потерь).

И отдельно — три уровня внутри текста, их тоже смешивают:

  • Набор символов (character set) — справочник, который каждому символу дает номер.
  • Кодовая точка (code point) — этот номер, например U+042F для буквы «Я».
  • Кодировка (encoding) — правило, по которому кодовая точка превращается в конкретные байты (UTF-8, UTF-16, cp1251).

Двоичное представление: биты и байты

Компьютер хранит и обрабатывает данные в двоичном виде — последовательностью двух состояний, которые записывают как 0 и 1. Один такой разряд называется битом. Физически это упрощение: за нулем и единицей стоят разные уровни сигнала или заряда, а не буквальное «есть ток / нет тока» — реализация зависит от типа памяти.

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

  • 1 бит — 2 варианта (0, 1);
  • 2 бита — 4 варианта (00, 01, 10, 11);
  • 8 бит — 256 вариантов.

Восемь бит — это байт, базовая единица адресации в большинстве систем. В один байт помещается число от 0 до 255. Любые данные — числа, текст, изображение, звук — в итоге превращаются в поток байт; различаются только правила, по которым это делается. Дальше в качестве сквозного примера берем текст, потому что на нем виднее разница между уровнями.

Кодировки текста: ASCII, Unicode, UTF-8

Чтобы записать текст байтами, нужна договоренность «какому символу какой номер и какие байты соответствуют». Исторически таких договоренностей было несколько.

ASCII — таблица на 7 бит, то есть 128 кодовых точек (номера 0-127). Их хватает на латиницу, цифры, знаки препинания и управляющие символы. Кириллицы, умляутов и прочего в ASCII нет.

Восьмибитные (однобайтовые) кодировки расширили ASCII до 256 символов: первые 128 совпадают с ASCII, а вторая половина отдана под национальные алфавиты. Отсюда «зоопарк» несовместимых таблиц — cp1251 и KOI-8 для кириллицы, ISO 8859-1 для западноевропейских языков и так далее. Один и тот же байт в разных таблицах означает разные буквы, поэтому текст, прочитанный «не той» кодировкой, превращается в мусор.

Unicode решает эту путаницу иначе. Это не «двухбайтовая кодировка», как часто пишут, а единый набор символов: он присваивает кодовую точку каждому символу всех письменностей. Диапазон кодовых точек — от U+0000 до U+10FFFF, реально назначено более 150 тысяч символов (Unicode 16.0, 2024). Сам по себе Unicode говорит только «какой символ под каким номером», но не «сколько это байт».

Байты задает уже кодировка Unicode — UTF-8, UTF-16 или UTF-32. Сегодня стандарт для веба и большинства файлов — UTF-8: она переменной длины, кодирует символ в 1-4 байта и обратно совместима с ASCII (первые 128 символов занимают ровно 1 байт, как в ASCII). Детальный разбор устройства UTF-8 и других форматов — в отдельной статье (ссылка в конце); здесь важно понять роль каждого уровня.

Сквозная цепочка: символ -> код-поинт -> байты

Соберем три уровня в одну линию на слове Яndex. В Python номер символа дает ord, а байты в UTF-8 — метод encode:

text = "Яndex"
for ch in text:
    cp = ord(ch)                      # кодовая точка
    b = ch.encode("utf-8")            # байты в UTF-8
    print(f"{ch!r:>6}  ->  U+{cp:04X}  ->  {b.hex(' ').upper()}")

data = text.encode("utf-8")
print("bytes:", data.hex(" ").upper())
print("decoded back:", data.decode("utf-8"))

Фактический вывод:

   'Я'  ->  U+042F  ->  D0 AF
   'n'  ->  U+006E  ->  6E
   'd'  ->  U+0064  ->  64
   'e'  ->  U+0065  ->  65
   'x'  ->  U+0078  ->  78
bytes: D0 AF 6E 64 65 78
decoded back: Яndex

Видно все три уровня: символ «Я», его кодовая точка U+042F и два байта D0 AF в UTF-8. Латинские буквы занимают по одному байту, совпадающему с их ASCII-кодом (n — это 6E). Поэтому строка из 5 символов заняла 6 байт: длина в символах и длина в байтах — разные величины.

Если те же байты прочитать другой кодировкой, получится мусор (mojibake) — потому что байты одни, а таблицы разные:

b = "Яndex".encode("utf-8")   # D0 AF 6E 64 65 78
print(b.decode("cp1251"))     # РЇndex

try:
    b.decode("ascii")
except UnicodeDecodeError as e:
    print("ascii error:", e)

Фактический вывод:

РЇndex
ascii error: 'ascii' codec can't decode byte 0xd0 in position 0: ordinal not in range(128)

Отсюда практическое правило: байты бессмысленны без указания кодировки, которой их читать. cp1251 «успешно» прочитала UTF-8-байты, но выдала неверный текст; ASCII честно упала, потому что байта 0xD0 в ней нет.

Оговорка про уровни: кодовая точка — это не всегда один видимый символ (графема). Некоторые эмодзи и буквы с диакритикой складываются из нескольких кодовых точек, поэтому len(строки) может не совпадать с числом символов, которые видит человек. Для базового текста эта разница не важна, но помнить о ней стоит.

Кодирование, шифрование и сжатие — в чем разница

Все три операции меняют форму данных, но с разной целью и с разной обратимостью. Различать их удобно по одному вопросу: нужен ли секрет, чтобы вернуть исходные данные.

Свойство Кодирование Шифрование Сжатие
Цель сменить форму записи скрыть содержимое уменьшить объем
Нужен секрет для обратного хода нет да, ключ нет
Кто может расшифровать любой, кто знает код только владелец ключа любой (для сжатия без потерь)
Меняет ли объем обычно нет специально обычно нет специально да, это и есть цель
Пример UTF-8, base64 AES, RSA ZIP, gzip

Небольшой прогон подтверждает грань «секрет не нужен» для кодирования и сжатия:

import base64, zlib

raw = "Привет, мир!".encode("utf-8")
enc = base64.b64encode(raw)                  # кодирование, обратимо без секрета
print("base64:", enc.decode())
print("обратно:", base64.b64decode(enc).decode("utf-8"))

long = ("данные " * 20).encode("utf-8")
comp = zlib.compress(long)                   # сжатие без потерь
print("сжатие:", len(long), "->", len(comp), "байт")
print("обратимо:", zlib.decompress(comp) == long)

Фактический вывод:

base64: 0J/RgNC40LLQtdGCLCDQvNC40YAh
обратно: Привет, мир!
сжатие: 260 -> 26 байт
обратимо: True

base64 — это именно кодирование: она превращает байты в набор безопасных ASCII-символов и разворачивается обратно без всякого ключа (это не защита данных). zlib сжала повторяющийся текст в 10 раз и вернула его без потерь. Ни в том, ни в другом случае секрет не нужен — в отличие от шифрования, где без ключа исходные данные не восстановить.

Выводы

  • Кодирование — это смена формы записи по открытым правилам; секрет для обратного преобразования не нужен, этим оно отличается от шифрования.
  • В основе всего лежит двоичное представление: бит — это 0/1, байт — 8 бит и 256 значений; любые данные в итоге становятся байтами.
  • В тексте различают три уровня: набор символов (кто под каким номером), кодовая точка (сам номер, U+042F) и кодировка (какими байтами номер записать).
  • Unicode — это набор символов, а не кодировка; конкретные байты задают UTF-8/UTF-16/UTF-32, сегодня по умолчанию — UTF-8 (1-4 байта, совместима с ASCII).
  • Байты нельзя правильно прочитать, не зная их кодировки: те же байты в другой таблице дают мусор (mojibake).

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

Понимание уровней «символ -> код-поинт -> байты» экономит часы отладки: кракозябры в консоли, письмах и импортах CSV почти всегда сводятся к чтению байт не той кодировкой, а не к «испорченному файлу». Тот же навык нужен при работе с файлами, сетевыми протоколами и базами данных, где кодировку задают явно.

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

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

Смежные темы: Кодировки символов и формат UTF-8, Кодирование и декодирование: что должен знать программист.

FAQ

Почему в файле «5 символов», а размер 6 байт?
Потому что в UTF-8 длина в байтах зависит от символов: латиница и цифры занимают 1 байт, кириллица — 2 байта, часть символов — 3-4 байта. Число символов и число байт совпадают только для чистого ASCII.

Можно ли по байтам однозначно понять, какая это кодировка?
Строго — нет. Иногда помогает BOM (метка порядка байт) в начале файла или эвристики, но универсального признака нет; поэтому кодировку принято указывать явно (в заголовке HTTP, атрибуте файла, параметре чтения).

base64 — это шифрование, раз текст стал нечитаемым?
Нет. base64 — кодирование: она разворачивается обратно без ключа, ее делает кто угодно. Для защиты данных нужно шифрование с ключом, а не base64.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)