Основная задача письменности с давних времен, отобразить визуально то, что человек произносит вербально. В истории встречается огромное количество примеров того, как люди, пытаясь передать через бумагу какую-то информацию, использовали для этого знакомые образы. Древние египтяне использовали иероглифы, очень похожие на вещи из повседневной жизни. 

Люди перешли от изображения быка (????) к финикийской нотации (????), которая со временем стала ассоциироваться с произношением слова (‘alp), как в случае с греческой альфой (α), пока она не стала латинской заглавной буквой (А).

Очень наглядно этот путь демонстрируют иероглифы: 
В случае с Китайским языком, визуальные образы не перешли в отдельный алфавит, где каждый символ обозначает определенный звук, а стали обозначать отдельные слова.

На бумаге мы можем комбинировать буквы в слова, слова в отдельные предложения, предложения в целые параграфы. Но если говорить о компьютерной технике, то тут мы используем символы, последовательность которых, образуют строки.
Но как эти строки понимает компьютер?

ASCII

Мы знаем что память компьютера представлена в виде нулей и единиц. Как в таком случае, он может понять, что такое символ, и какие именно символы нужны, чтобы отобразить какой-то текст?

Чтобы решить эту проблему, программисты придумали таблицу символов ASCII (American standard code for information interchange)

Во времена изобретения UNIX системы, символы выделяли лишь 8 бит памяти. В те дни использование памяти имело большое значение, потому что она у компьютеров того времени была в дефиците. Написание чисел в виде байт-кода, не самая удобная задача для программиста. ASCII решал эту проблему и по своей сути, представляет таблицу поиска байтов в символы. Каждый символ этой таблицы относится к своему номеру, и может быть представлен в виде двоичного числа.

01100010 01101001 01110100 01110011
b        i        t        s

В этой ASCII таблице записано всего 95 буквенно-цифровых символа, чего вполне достаточно, если вы говорите по-английски. Для экономии места, ASCII использует для отображения английского языка всего 7 бит (0-127) вместо 8, чего вполне достаточно для этих 95 символов.

Но что же делать с другими языками? Как отобразить их?

Для решения этой проблемы, программисты из разных стран, решили, что можно использовать незанятое место в ASCII байте (128-255) и использовать свободное место для символов из других языков. Некоторые языки пошли дальше, и для кодировки текста стали использовать 16 бит, если 8 бит было недостаточно.
К концу 90х было примерно 60 разных стандартов кодировки.

Пример ASCII таблицы с символами русского языка
Пример ASCII таблицы с символами русского языка

И действительно для большинства языков мира это сработало. Однако до того момента пока этот файлы оставались локально на компьютере (да-да, интернет был не всегда)

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

�����[ Éf����Õì ÔǵÇ���¢!!

WEB

Чтобы как-то справится с этой проблемой был добавлен специальный HTML тег, позволяющий указывать определенную кодировку. Если этого тега не было, клиент мог получить кодировку в ответ от сервера. Однако самое веселье начиналось тогда, когда все эти значения были разные. Поэтому в старых версиях Google Chrome можно было вручную изменить тип кодировки.

DNS

Появилась проблема в доменных именах. DNS сервера понимали только первые 7 бит ASCII кодировки, что сильно ограничивало пространство доменных имен. Для решения этой проблемы даже был придуман Punycode.  Который позволял кодировать 8/16/32 битные символы, используя значения из ASCII таблицы. 

Пример

Например доменное имя: мойлюбимыйсайт.ру в Punycode кодировке выглядел бы вот так: xn--80abvccbjgep3bo6iqb.xn--p1ag:

MAIL

Так же проблемы неправильной кодировки настигли почтовые сервисы. Протокол SMTP который использовался для почтовых клиентов в качестве протокола передачи почты, так же понимал только 7 битную ASCII кодировку. 

Все эти проблемы подтолкнули программистов к созданию нового стандарта Unicode, который включал в себя решение проблемы с кодировкой большого количества символов, а так же, оставался эффективным в плане объема передаваемых данных.

Unicode

Unicode - стандарт для обработки, кодировки и представления текста во всем мире.
Юникод хранит свои значения не в глифах, а в графемах. Каждая графема имеет свой Code Unit. Code Unit - в общем понимании, это уникальный номер, который присваивается каждой графеме. Обычно Code Point записывается в виде U+ и затем идут 4-6
шестнадцатеричные цифры. Например, Code Unit символа заглавной латинской буквы A это - U+0041

Вместо того чтобы попытаться закодировать все возможные комбинации, Unicode предоставляет динамическую композицию.
Например, символ é может быть представлен как единственный символ U+00E9 (É) , либо в виде комбинации U+0065 (E) вместе с U+0301 (◌́) . Для динамической композиции используются так называемые суррогаты - спец символы которые используются для комбинации с другими символами.
Все это сделано для того, чтобы юникод был максимально гибким и мог собрать текст как по кирпичикам, отвечая только за его логическую суть. Вся ответственность за визуальную часть: размер, форма, шрифт, стиль, ложится на плечи устройства, которое декодирует Unicode для последующего отображения.

Пример UNICODE
Пример UNICODE

Еще один плюс Unicode, так это то, что он был придуман с сохранением обратной совместимости с ASCII и все сайты которые использовали ASCII не пострадали. (Остальным пришлось адаптироваться)

Секторы Unicode

Юникод определяет кодовое пространство размером от U+0000 до U+10FFFF. Что равно примерно 1,114,112 символам которые поделены на 17 отдельных секторов. Каждый сектор хранит 2¹⁶ или 65,536 символов.

Сектор 0 (U+0000 –U+FFFF): Базовый мультиязычный сектор
Содержит самые часто используемые символы, в том числе латинские, кириллицу, арамейские, брахмические, и другие различные символы.
Сектор 1 (U+10000– U+1FFFF): Дополнительным многоязычный сектор Содержит исторические символы, такие как египетские иероглифы, изобразительные символы, как ноты и игральные карты, а также смайлики.
Сектор 2 (U+20000- U+2FFFF): Дополнительным идеографический сектор
Содержит китайские, японские и корейские иероглифы, которые не были добавлены как часть более ранних стандартов.
Сектора с 3 по 13 (U+30000 — U+DFFFF):
В настоящее время пусты.
Сектор 14 (U+E0000 - U+EFFFF): Дополнительный сектор специального назначения.
В частности, здесь содержат символы региональных индикаторов, используемые для формирования флага страны, например, в эмодзи.
Сектора 15 и 16 (U+F0000 - U+10FFFF):
Обозначены как области частного использования, а так же в этой области хранятся символы суррогаты о которых говорилось ранее.

UTF-8/UTF-16/UTF-32

Вместе с Unicode идет несколько механизмов для кодирования данных, такие как UTF-8, UTF-16, UTF-32.

  • UTF-8 диапазон 1 до 4 байт

  • UTF-16 диапазон 2 или 4 байт.

  • UTF-32 фиксированный размер 4 байта.

UTF-8

UTF-8 хранит каждый символ Unicode, используя от 1 до 4
8-битных единицы. В таблице снизу показано, как диапазоны скалярных
значения представлены в UTF-8:

UTF-16

UTF-16 хранит каждый символ Unicode в 1 или 2 16-битных единицах. В следующей таблице показано, как распределяются диапазоны скалярных значений в UTF-16:

UTF-32

UTF-32 представляет каждый символ Unicode в одном 32-битной единице. В следующей ниже показано, как распределяются диапазоны скалярных значений в UTF-32:

UTF-8 и UTF-16 имеют динамическую длину, в то время когда UTF-32 всегда имеет фиксированный размер. UTF-8 Сохраняет очень много места. Если большинство текста написано на английском языке, и может поместиться в размер 1 байт, то UTF-8 более предпочтительный выбор, так как может сократить объем передаваемых данных в 4 раза если сравнивать с UTF-32.

Отсюда мог возникнуть вопрос. А зачем нам нужны другие виды кодирования Unicode, когда есть UTF-8, который и так имеет динамическую длину и может отобразить почти любой символ?

Тут можно провесит аналогию с автомобилем: если нам нужно перевезти груз, который отлично помещается в легковой автомобиль, то нет смысла делать это используя целый грузовик. Он поедет почти пустым. В то время когда легковой автомобиль компактнее и экономичнее. Если вдруг груза становится слишком много, мы можем позвать друга, и тогда мы поедем на двух автомобилях, что тоже решит нашу проблему. Но иногда мы хотим быть уверены, что все что нам нужно, точно поместиться в транспортное средство. Тогда грузовик - наш выбор.

Исходя из примера выше, можно сделать вывод: когда нам нужно использовать более гибкий и экономичный вид кодировки для передачи информации - UTF-8/UTF-16 отлично для этого подойдут. Однако иногда, если мы хотим точно быть уверены, что каждый наш символ занимает фиксированный размер - тогда лучше использовать UTF-32.

На этом моменте могло сложиться что Unicode идеальное решение. Однако это не так. У него тоже есть проблемы. В зависимости от операционной системы и языка программирования, Unicode обрабатывается по-разному, и в некоторых случаях, длина строки, либо количество символов в ней может отличаться. Тут и начинается самое веселье, которое выпало на долю разработчиков. Unicode ещё не завершён. Как и в случае с любым стандартом, мы что-то добавляем, убираем, предлагаем новое. Никакие спецификации нельзя назвать «завершёнными». Обычно в год бывает 1-2 релиза

Например в Unicode буквы "E" и "É", как говорилось ранее, можно записывать по-разному - одним символом и двумя символами "E" + глиф. Разница в том что: 

  • У символов будет размер разный.

  • При неправильной кодировке двойной клик на слове с такими комбинированным "É" выделяет слово не полностью.

  • Может некорректно отрабатывать сортировка.

На этом примере мы можем догадаться, что не все программы и сайты умеют правильно приводить Code Points к такому виду, который позволяет сравнивать одинаковые глифы, записанные с использованием разных Code Points. Иными словами, не каждая программа и сайт распознает «É» и «É» за один символ, из-за чего становится невозможно, например, производить поиск по таким буквам.

TL;DR

Юникод — это общий массив символов для всех языков мира, глифов и эмодзи. Семейство кодировок UTF — это то, как компьютеры узнают, какая последовательность битов должна быть представлена чтобы получить нужный символ. Однако каждый язык программирования, приложение и ОС реализуют и поддерживают Unicode по-разному (если вообще используют). Вот где начинается веселая жизнь разработчика ????.

Комментарии (6)


  1. NeoCode
    02.02.2023 23:31
    +3

    А еще Юникод несовершенен)) Как впрочем и все технологии, он имеет свои недостатки, которые становятся понятны только по прошествии некоторого времени, когда появляется какой-то материал для осмысления и множество идей, как можно было сделать лучше. Но боюсь, что это не тот случай - делать новую кодировку общемирового масштаба просто ради перфекционизма никто не будет...


    1. regint
      03.02.2023 08:22
      +1

      Ну же, расскажите, я уверен чтоя не единственный кому интересно прочесть по поводу тех проблем, о которых вам известно))


  1. roqin
    03.02.2023 09:04
    +1

    Вместе с Unicode идет несколько механизмов для кодирования данных, такие как UTF-8, UTF-16, UTF-32.

    А с UTF-7 что произошло? ????

    Тут ещё забавно, что некоторые его поддерживают, но не до конца - считают, что в нём только 65536 символов (т.е. только BMP), хоть и UTF-8 используется.


  1. a-tk
    03.02.2023 09:59
    +1

    Joel Spolski об этом всём писал в 2003 году: https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/

    С того времени принципиально ничего не поменялось, только добавилось немножко заполнение плоскостей unicode и появилось больше языков, которые всё так же плохо работают со строками переменной длины.


    1. a-tk
      03.02.2023 10:01
      +1

      О, даже перевод на Хабре в песочнице есть 10-летней давности

      https://habr.com/ru/sandbox/47663/


  1. AlexGorky
    03.02.2023 13:37
    +1

    Для меня было откровением, что в unicode можно буквы "й" и "ё" записать по-разному - одним символом и двумя символами ("и" + глиф, "е" + глиф):

    • Размер разный

    • Двойной клик на слове с такими "слепленными" "й" и "ё" выделяет только половину слова

    • Про сортировку и прочее вообще молчу.

    См. https://habr.com/ru/post/262679/

    Если получится, то вот:

    плойка - 1 символ
    плойка - 2 символа