Основная задача письменности с давних времен, отобразить визуально то, что человек произносит вербально. В истории встречается огромное количество примеров того, как люди, пытаясь передать через бумагу какую-то информацию, использовали для этого знакомые образы. Древние египтяне использовали иероглифы, очень похожие на вещи из повседневной жизни.
Люди перешли от изображения быка (????) к финикийской нотации (????), которая со временем стала ассоциироваться с произношением слова (‘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 разных стандартов кодировки.
И действительно для большинства языков мира это сработало. Однако до того момента пока этот файлы оставались локально на компьютере (да-да, интернет был не всегда)
Сами по себе документы очень редко обозначали свою кодировку. Когда люди начали обмениваться файлами, то на другом конце, получатель мог просто не знать о том формате, которым пользовался отправитель и в попытке прочитать полученный файл, получал очень странный результат:
�����[ Éf����Õì ÔǵÇ���¢!!
WEB
Чтобы как-то справится с этой проблемой был добавлен специальный HTML тег, позволяющий указывать определенную кодировку. Если этого тега не было, клиент мог получить кодировку в ответ от сервера. Однако самое веселье начиналось тогда, когда все эти значения были разные. Поэтому в старых версиях Google Chrome можно было вручную изменить тип кодировки.
DNS
Появилась проблема в доменных именах. DNS сервера понимали только первые 7 бит ASCII кодировки, что сильно ограничивало пространство доменных имен. Для решения этой проблемы даже был придуман Punycode. Который позволял кодировать 8/16/32 битные символы, используя значения из ASCII таблицы.
Пример
Например доменное имя: мойлюбимыйсайт.ру в Punycode кодировке выглядел бы вот так: xn--80abvccbjgep3bo6iqb.xn--p1ag:
Так же проблемы неправильной кодировки настигли почтовые сервисы. Протокол 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, так это то, что он был придуман с сохранением обратной совместимости с 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)
roqin
03.02.2023 09:04+1Вместе с Unicode идет несколько механизмов для кодирования данных, такие как UTF-8, UTF-16, UTF-32.
А с UTF-7 что произошло? ????
Тут ещё забавно, что некоторые его поддерживают, но не до конца - считают, что в нём только 65536 символов (т.е. только BMP), хоть и UTF-8 используется.
a-tk
03.02.2023 09:59+1Joel 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 и появилось больше языков, которые всё так же плохо работают со строками переменной длины.
AlexGorky
03.02.2023 13:37+1Для меня было откровением, что в unicode можно буквы "й" и "ё" записать по-разному - одним символом и двумя символами ("и" + глиф, "е" + глиф):
Размер разный
Двойной клик на слове с такими "слепленными" "й" и "ё" выделяет только половину слова
Про сортировку и прочее вообще молчу.
См. https://habr.com/ru/post/262679/
Если получится, то вот:
плойка - 1 символ
плойка - 2 символа
NeoCode
А еще Юникод несовершенен)) Как впрочем и все технологии, он имеет свои недостатки, которые становятся понятны только по прошествии некоторого времени, когда появляется какой-то материал для осмысления и множество идей, как можно было сделать лучше. Но боюсь, что это не тот случай - делать новую кодировку общемирового масштаба просто ради перфекционизма никто не будет...
regint
Ну же, расскажите, я уверен чтоя не единственный кому интересно прочесть по поводу тех проблем, о которых вам известно))