mirror of
https://github.com/krahets/hello-algo.git
synced 2026-04-04 03:00:06 +08:00
14 KiB
14 KiB
# Кодирование символов *
В компьютере все данные хранятся в виде двоичных чисел, и символы `char` не являются исключением. Для представления символов необходимо создать «набор символов», определяющий взаимно однозначное соответствие между каждым символом и двоичным числом. Имея набор символов, компьютер может выполнять преобразование двоичных чисел в символы путем поиска в таблице.
## Набор символов ASCII
<u>Код ASCII</u> является самым ранним набором символов, его полное название — American Standard Code for Information Interchange (Американский стандартный код для обмена информацией). Он использует 7-битные двоичные числа (младшие 7 бит одного байта) для представления символа и может представлять максимум 128 различных символов. Как показано на рисунке ниже, код ASCII включает прописные и строчные буквы английского алфавита, цифры 0~9, некоторые знаки препинания, а также управляющие символы (такие как символ новой строки и табуляции).

Однако **код ASCII может представлять только английский язык**. С глобализацией компьютеров появился набор символов <u>EASCII</u>, способный представлять больше языков. Он расширил 7-битный ASCII до 8 бит и может представлять 256 различных символов.
По всему миру последовательно появились различные наборы символов EASCII, подходящие для разных регионов. Первые 128 символов этих наборов унифицированы как код ASCII, а последние 128 символов определены по-разному для удовлетворения потребностей различных языков.
## Набор символов GBK
Позже люди обнаружили, что **код EASCII все еще не может удовлетворить требования к количеству символов во многих языках**. Например, существует почти сто тысяч китайских иероглифов, из которых несколько тысяч используются в повседневной жизни. В 1980 году Главное управление стандартизации Китая выпустило набор символов <u>GB2312</u>, который включал 6763 китайских иероглифа и в основном удовлетворял потребности компьютерной обработки китайских иероглифов.
Однако GB2312 не мог обрабатывать некоторые редкие иероглифы и традиционные китайские символы. Набор символов <u>GBK</u> был получен путем расширения GB2312 и включает в общей сложности 21886 китайских иероглифов. В схеме кодирования GBK символы ASCII представлены одним байтом, а китайские иероглифы — двумя байтами.
## Набор символов Unicode
С бурным развитием компьютерных технологий наборы символов и стандарты кодирования расцвели пышным цветом, что привело ко многим проблемам. С одной стороны, эти наборы символов обычно определяли только символы конкретного языка и не могли нормально работать в многоязычной среде. С другой стороны, для одного и того же языка существовало несколько стандартов наборов символов, и если два компьютера использовали разные стандарты кодирования, при передаче информации возникала кракозябра.
Исследователи того времени думали: **если создать достаточно полный набор символов, включающий все языки и символы со всего мира, разве это не решит проблемы многоязычной среды и кракозябры**? Под влиянием этой идеи появился большой и всеобъемлющий набор символов Unicode.
<u>Unicode</u> на китайском языке называется «统一码» (унифицированный код), теоретически может вместить более 1 миллиона символов. Он стремится включить символы со всего мира в единый набор символов, предоставляя универсальный набор символов для обработки и отображения текстов на различных языках, уменьшая проблемы с кракозяброй, возникающие из-за различий в стандартах кодирования.
С момента выпуска в 1991 году Unicode постоянно пополняется новыми языками и символами. По состоянию на сентябрь 2022 года Unicode уже включает 149186 символов, включая символы различных языков, знаки и даже эмодзи. В огромном наборе символов Unicode часто используемые символы занимают 2 байта, а некоторые редкие символы занимают 3 или даже 4 байта.
Unicode является универсальным набором символов, по сути присваивающим каждому символу номер (называемый «кодовой точкой»), **но он не определяет, как эти кодовые точки символов должны храниться в компьютере**. Мы не можем не задаться вопросом: когда кодовые точки Unicode различной длины одновременно появляются в тексте, как система анализирует символы? Например, при заданном коде длиной 2 байта, как система определяет, является ли это одним 2-байтовым символом или двумя 1-байтовыми символами?
Для вышеуказанных проблем **прямым решением является хранение всех символов в виде кодов одинаковой длины**. Как показано на рисунке ниже, каждый символ в «Hello» занимает 1 байт, каждый символ в «算法» занимает 2 байта. Мы можем закодировать все символы в «Hello 算法» длиной 2 байта, заполнив старшие биты нулями. Таким образом, система может анализировать один символ каждые 2 байта и восстановить содержание этой фразы.

Однако код ASCII уже доказал нам, что для кодирования английского языка требуется только 1 байт. Если использовать вышеуказанную схему, размер английского текста будет в два раза больше, чем при кодировании ASCII, что очень расточительно с точки зрения памяти. Поэтому нам нужен более эффективный метод кодирования Unicode.
## Кодирование UTF-8
В настоящее время UTF-8 стал наиболее широко используемым методом кодирования Unicode в мире. **Это кодирование переменной длины**, использующее от 1 до 4 байтов для представления символа в зависимости от сложности символа. Символы ASCII требуют только 1 байт, латинские и греческие буквы требуют 2 байта, часто используемые китайские иероглифы требуют 3 байта, а некоторые другие редкие символы требуют 4 байта.
Правила кодирования UTF-8 не сложны и делятся на следующие два случая.
- Для символов длиной 1 байт старший бит устанавливается в $0$, остальные 7 бит устанавливаются в кодовую точку Unicode. Стоит отметить, что символы ASCII занимают первые 128 кодовых точек в наборе символов Unicode. Это означает, что **кодирование UTF-8 обратно совместимо с кодом ASCII**. Это означает, что мы можем использовать UTF-8 для анализа старых текстов в кодировке ASCII.
- Для символов длиной $n$ байт (где $n > 1$) старшие $n$ бит первого байта устанавливаются в $1$, бит $n + 1$ устанавливается в $0$; начиная со второго байта, старшие 2 бита каждого байта устанавливаются в $10$; все остальные биты используются для заполнения кодовой точки Unicode символа.
На рисунке ниже показано кодирование UTF-8, соответствующее «Hello算法». Наблюдая, можно обнаружить, что поскольку старшие $n$ бит все установлены в $1$, система может определить длину символа $n$, прочитав количество единиц в старших битах.
Но почему старшие 2 бита всех остальных байтов устанавливаются в $10$? На самом деле, это $10$ может служить контрольным символом. Предположим, система начинает анализировать текст с неправильного байта, $10$ в начале байта может помочь системе быстро обнаружить аномалию.
Причина использования $10$ в качестве контрольного символа заключается в том, что согласно правилам кодирования UTF-8 невозможно, чтобы старшие два бита символа были $10$. Этот вывод можно доказать методом от противного: предположим, что старшие два бита символа равны $10$, это означает, что длина символа равна $1$, что соответствует коду ASCII. Но старший бит кода ASCII должен быть $0$, что противоречит предположению.

Помимо UTF-8, распространенные методы кодирования включают следующие два.
- **Кодирование UTF-16**: использует 2 или 4 байта для представления символа. Все символы ASCII и часто используемые неанглийские символы представлены 2 байтами; небольшое количество символов требует 4 байта. Для 2-байтовых символов кодирование UTF-16 равно кодовой точке Unicode.
- **Кодирование UTF-32**: каждый символ использует 4 байта. Это означает, что UTF-32 занимает больше места, чем UTF-8 и UTF-16, особенно для текстов с высокой долей символов ASCII.
С точки зрения занимаемого пространства хранения использование UTF-8 для представления английских символов очень эффективно, поскольку требуется только 1 байт;