Краткий обзор системы Unicode, UTF, ASCII и кодировки

У компьютеров есть свой язык. Они не понимают человеческих языков (возможно, понимают, если вы говорите на двоичном языке), и они не знают ничего, кроме двоичного. Как мы с ними общаемся?

Пока я печатаю, компьютер не распознает ни одного из символов, которые вы видите. Рассмотрим символ M. На самом низком уровне M хранится с использованием последовательности нулей и единиц: 01001101. Прежде чем продолжить, напомним два основных определения:

Бит - наименьшая единица хранения, может хранить только 0 или 1
Байт - один байт = восемь бит. Вот и все.

Юникод

Стандарт Юникода:

«Стандарт Unicode - это система кодирования символов, разработанная для поддержки всемирного обмена, обработки и отображения письменных текстов на различных языках и технических дисциплинах современного мира. Кроме того, он поддерживает классические и исторические тексты на многих письменных языках ».

Проще говоря, Unicode присваивает каждому символу уникальный номер (называемый кодовой точкой), независимо от платформы, программы или чего-либо еще.

Набор символов

Набор символов - это фиксированный набор символов. Например, от أ до ي - это набор символов, представляющий арабский алфавит.

Другой пример - знаменитая таблица ASCII: семибитный код символа, где каждая последовательность представляет собой уникальный символ. ASCII может представлять 27 (= 128) символов (включая непечатаемые), но, к сожалению, он не может представлять любовь 💔 😔, иврит, русский, арабский алфавиты и другие полезные символы. Но почему?

Поскольку для правильного хранения любой файл должен пройти кодирование / декодирование, ваш компьютер должен знать, как преобразовать набор символов системы письма вашего языка в последовательности нулей и единиц. Этот процесс называется кодировкой символов. Вы можете рассматривать его как таблицу. Чтобы вы могли понять, что означает «таблица», взгляните на изображение ниже:

Символ A представлен 65-разрядным десятичным значением (что составляет 1000001 в семибитном двоичном формате). Итак, теперь вопрос в том, как нам представить символы, выходящие за пределы этого диапазона?

Системы кодирования

Очень важно различать концепции набора символов и системы кодирования. Первый - это просто набор символов, которые вы можете использовать, а второй - способ хранения этих символов в памяти (в виде потока байтов), поэтому для данной кодировки может быть более одной кодировки.

Как и в схеме ASCII, существует множество других систем кодирования:

  • UTF-8
  • UTF-16
  • UTF-32
  • EUC

В этой статье мы поговорим о системах UTF-X.

UTF-32

Эта схема требует 32 бита (четыре байта) для кодирования любого символа. Например, чтобы представить кодовую точку символа A с помощью этой схемы, нам нужно записать 65 в 32-битном двоичном числе:

00000000 00000000 00000000 01000001

Если вы посмотрите внимательнее, то заметите, что самые правые семь битов на самом деле одинаковы при использовании схемы ASCII. Но поскольку UTF-32 - это схема с фиксированной шириной, мы должны присоединить три дополнительных байта. Это означает, что если у нас есть два файла, которые содержат только символ A, один из них закодирован в ASCII, а другой - в кодировке UTF-32, и их размер будет составлять один и четыре байта соответственно.

Эта схема не подходила для англоговорящих, потому что теперь файлы, содержащие только символы ASCII, скажем, с общим размером X байтов, превратятся в монстров с четырехкратным байтом, что является огромной тратой памяти.

UTF-16 (+ LE и BE)

Пришло еще одно решение в виде UTF-16. Вы можете подумать, что так же, как UTF-32 использует 32-битную фиксированную ширину для представления кодовой точки, UTF-16 - это 16-битная фиксированная ширина. Неправильный!

В UTF-16 кодовая точка может быть представлена ​​в формате 16 бит или 32 бит. Итак, эта схема представляет собой систему кодирования переменной длины. В чем преимущество перед UTF-32? По крайней мере, для ASCII размер файлов не будет в четыре раза больше исходного (но все равно в два раза), поэтому мы по-прежнему не имеем обратной совместимости с ASCII.

Поскольку семи битов достаточно для представления символа A, теперь мы можем использовать два байта вместо четырех, как в UTF-32.

00000000 01000001

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

01000001 00000000

Что ж, можем. Некоторые компании действительно используют эту кодировку. Давайте попробуем сымитировать компьютер, когда он пытается прочитать 16 бит. Ниже приведена простая программа на C, которая выделяет восемь целых чисел в последовательности:

int *p;
p = (int *) malloc(8 * sizeof(int));

ОС вернет адрес первого байта. Таким образом, p будет указывать на первое место, и, увеличивая его, мы получим следующие байты. Предположим, мы храним данные в этой форме (два нижних поля представляют два байта):

+---+---+
| 0 | A |
+---+---+

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

+---+---+
| A | 0 |
+---+---+

Второй формат называется обратным порядком байтов (BE) (данные хранятся в старшем байте), а последний называется обратным порядком байтов (LE) (младший байт ).

UTF-8

Вы угадали: в UTF-8 кодовая точка может быть представлена ​​как 32, 16, 24, или восемь бит, и, как и система UTF-16, она также является системой кодирования переменной длины.

Наконец, мы можем представить A так же, как мы представляем его, используя систему кодирования ASCII:

01001101

Детская площадка

Откройте ваш любимый текстовый редактор (Vim) и создайте файл, содержащий символ A. Посмотрим его кодировку:

$ xxd -b test.txt
0000000: 0100001 00001010

Первый 0000000 не важен для нашего анализа; он обозначает смещение. Байт после него - это двоичное представление символа A в кодировке ASCII (семь бит). Наконец, последний бит - это кодировка ASCII перевода строки (проверьте, проверив таблицу ASCII - найдите десятичное значение этого числа). Теперь проверим размер файла:

$ du -b test.txt | cut -f1
2

Большой! Два байта, как и ожидалось (не забудьте автоматически добавить перевод строки). Продолжаем играть - теперь убираем A и вставляем символ δ:

$ file test.txt
test.txt: UTF-8 Unicode text

Теперь размер меняется:

$ du -b test.txt | cut -f1
3

Почему 3? Посмотрим бинарный дамп файла:

$ xxd -b test.txt
0000000: 11001110 10110100 00001010

Давайте проигнорируем последний байт, который представляет перевод строки, и сосредоточимся на фактической кодировке символа δ:

11001110 10110100

Напомним, что если первые три бита равны 110, то символ кодируется с использованием двух байтов. Двоичное представление δ:

11001110 10110100

Именно это мы и получили.

UTF-8 против UTF-16

И UTF-8, и UTF-16 имеют кодировку переменной длины. Кодировка UTF-8 может занимать минимум восемь бит, в то время как для UTF-16 требуется минимум 16 бит.

Для основных символов ASCII UTF-8 будет использовать только один байт на символ, тогда как кодировка UTF-16 будет использовать два байта (что делает UTF-8 обратно совместимым с ASCII).

Теперь поговорим о случаях, когда кодировка UTF-8 занимает больше байтов, чем UTF-16. Рассмотрим китайскую букву «語» - ее кодировка UTF-8:

11101000 10101010 10011110

Хотя его кодировка UTF-16 короче:

10001010 10011110

Надеюсь, вы сочтете это полезным.