Что такое unicode в чем достоинства и недостатки использования этого семейства кодировок
Юникод: необходимый практический минимум для каждого разработчика
Юникод — это очень большой и сложный мир, ведь стандарт позволяет ни много ни мало представлять и работать в компьютере со всеми основными письменностями мира. Некоторые системы письма существуют уже более тысячи лет, причём многие из них развивались почти независимо друг от друга в разных уголках мира. Люди так много всего придумали и оно зачастую настолько непохоже друг на друга, что объединить всё это в единый стандарт было крайне непростой и амбициозной задачей.
Чтобы по-настоящему разобраться с Юникодом нужно хотя бы поверхностно представлять себе особенности всех письменностей, с которыми позволяет работать стандарт. Но так ли это нужно каждому разработчику? Мы скажем, что нет. Для использования Юникода в большинстве повседневных задач, достаточно владеть разумным минимумом сведений, а дальше углубляться в стандарт по мере необходимости.
В статье мы расскажем об основных принципах Юникода и осветим те важные практические вопросы, с которыми разработчики непременно столкнутся в своей повседневной работе.
Зачем понадобился Юникод?
До появления Юникода, почти повсеместно использовались однобайтные кодировки, в которых граница между самими символами, их представлением в памяти компьютера и отображением на экране была довольно условной. Если вы работали с тем или иным национальным языком, то в вашей системе были установлены соответствующие шрифты-кодировки, которые позволяли отрисовывать байты с диска на экране таким образом, чтобы они представляли смысл для пользователя.
Если вы распечатывали на принтере текстовый файл и на бумажной странице видели набор непонятных кракозябр, это означало, что в печатающее устройство не загружены соответствующие шрифты и оно интерпретирует байты не так, как вам бы этого хотелось.
У такого подхода в целом и однобайтовых кодировок в частности был ряд существенных недостатков:
Основные принципы Юникода
Все мы прекрасно понимаем, что компьютер ни о каких идеальных сущностях знать не знает, а оперирует битами и байтами. Но компьютерные системы пока создают люди, а не машины, и для нас с вами иногда бывает удобнее оперировать умозрительными концепциями, а затем уже переходить от абстрактного к конкретному.
Важно! Одним из центральных принципов в философии Юникода является чёткое разграничение между символами, их представлением в компьютере и их отображением на устройстве вывода.
Вводится понятие абстрактного юникод-символа, существующего исключительно в виде умозрительной концепции и договорённости между людьми, закреплённой стандартом. Каждому юникод-символу поставлено в соответствие неотрицательное целое число, именуемое его кодовой позицией (code point).
Так, например, юникод-символ U+041F — это заглавная кириллическая буква П. Существует несколько возможностей представления данного символа в памяти компьютера, ровно как и несколько тысяч способов отображения его на экране монитора. Но при этом П, оно и в Африке будет П или U+041F.
Это хорошо нам знакомая инкапсуляция или отделение интерфейса от реализации — концепция, отлично зарекомендовавшая себя в программировании.
Получается, что руководствуясь стандартом, любой текст можно закодировать в виде последовательности юникод-символов
записать на листочке, упаковать в конверт и переслать в любой конец Земли. Если там знают о существовании Юникода, то текст будет воспринят ими ровно так же, как и нами с вами. У них не будет ни малейших сомнений, что предпоследний символ — это именно кириллическая строчная е (U+0435), а не скажем латинская маленькая e (U+0065). Обратите внимание, что мы ни слова не сказали о байтовом представлении.
Хотя юникод-символы и называются символами, они далеко не всегда соответствуют символу в традиционно-наивном понимании, например букве, цифре, пунктуационному знаку или иероглифу. (Подробнее смотри под спойлером.)
Что такое символ, чем отличается графемный кластер (читай: воспринимаемое как единое целое изображение символа) от юникод-символа и от кодового кванта мы расскажем в следующий раз.
Кодовое пространство Юникода
Кодовое пространство Юникода состоит из 1 114 112 кодовых позиций в диапазоне от 0 до 10FFFF. Из них к девятой версии стандарта значения присвоены лишь 128 237. Часть пространства зарезервирована для частного использования и консорциум Юникода обещает никогда не присваивать значения позициям из этих специальный областей.
Ради удобства всё пространство поделено на 17 плоскостей (сейчас задействовано шесть их них). До недавнего времени было принято говорить, что скорее всего вам придётся столкнуться только с базовой многоязыковой плоскостью (Basic Multilingual Plane, BMP), включающей в себя юникод-символы от U+0000 до U+FFFF. (Забегая немного вперёд: символы из BMP представляются в UTF-16 двумя байтами, а не четырьмя). В 2016 году этот тезис уже вызывает сомнения. Так, например, популярные символы Эмодзи вполне могут встретиться в пользовательском сообщении и нужно уметь их корректно обрабатывать.
Кодировки
Если мы хотим переслать текст через Интернет, то нам потребуется закодировать последовательность юникод-символов в виде последовательности байтов.
Стандарт Юникода включает в себя описание ряда юникод-кодировок, например UTF-8 и UTF-16BE/UTF-16LE, которые позволяют кодировать всё пространство кодовых позиций. Конвертация между этими кодировками может свободно осуществляться без потерь информации.
Также никто не отменял однобайтные кодировки, но они позволяют закодировать свой индивидуальный и очень узкий кусочек юникод-спектра — 256 или менее кодовых позиций. Для таких кодировок существуют и доступны всем желающим таблицы, где каждому значению единственного байта сопоставлен юникод-символ (см. например CP1251.TXT). Несмотря на ограничения, однобайтные кодировки оказываются весьма практичными, если речь идёт о работе с большим массивом моноязыковой текстовой информации.
Из юникод-кодировок самой распространённой в Интернете является UTF-8 (она завоевала пальму первенства в 2008 году), главным образом благодаря её экономичности и прозрачной совместимости с семибитной ASCII. Латинские и служебные символы, основные знаки препинания и цифры — т.е. все символы семибитной ASCII — кодируются в UTF-8 одним байтом, тем же, что и в ASCII. Символы многих основных письменностей, не считая некоторых более редких иероглифических знаков, представлены в ней двумя или тремя байтами. Самая большая из определённых стандартом кодовых позиций — 10FFFF — кодируется четырьмя байтами.
Обратите внимание, что UTF-8 — это кодировка с переменной длиной кода. Каждый юникод-символ в ней представляется последовательностью кодовых квантов с минимальной длиной в один квант. Число 8 означает битовую длину кодового кванта (code unit) — 8 бит. Для семейства кодировок UTF-16 размер кодового кванта составляет, соответственно, 16 бит. Для UTF-32 — 32 бита.
Если вы пересылаете по сети HTML-страницу с кириллическим текстом, то UTF-8 может дать весьма ощутимый выигрыш, т.к. вся разметка, а также JavaScript и CSS блоки будут эффективно кодироваться одним байтом. К примеру главная страница Хабра в UTF-8 занимает 139Кб, а в UTF-16 уже 256Кб. Для сравнения, если использовать win-1251 с потерей возможности сохранять некоторые символы, то размер, по сравнению с UTF-8, сократится всего на 11Кб до 128Кб.
Для хранения строковой информации в приложениях часто используются 16-битные юникод-кодировки в силу их простоты, а так же того факта, что символы основных мировых систем письма кодируются одним шестнадцатибитовым квантом. Так, например, Java для внутреннего представления строк успешно применяет UTF-16. Операционная система Windows внутри себя также использует UTF-16.
В любом случае, пока мы остаёмся в пространстве Юникода, не так уж и важно, как хранится строковая информация в рамках отдельного приложения. Если внутренний формат хранения позволяет корректно кодировать все миллион с лишним кодовых позиций и на границе приложения, например при чтении из файла или копировании в буфер обмена, не происходит потерь информации, то всё хорошо.
Для корректной интерпретации текста, прочитанного с диска или из сетевого сокета, необходимо сначала определить его кодировку. Это делается либо с использованием метаинформации, предоставленной пользователем, записанной в тексте или рядом с ним, либо определяется эвристически.
В сухом остатке
Информации много и имеет смысл привести краткую выжимку всего, что было написано выше:
Краткое замечание про кодирование
С термином кодирование может произойти некоторая путаница. В рамках Юникода кодирование происходит дважды. Первый раз кодируется набор символов Юникода (character set), в том смысле, что каждому юникод-символу ставится с соответствие кодовая позиция. В рамках этого процесса набор символов Юникода превращается в кодированный набор символов (coded character set). Второй раз последовательность юникод-символов преобразуется в строку байтов и этот процесс также называется кодирование.
В англоязычной терминологии существуют два разных глагола to code и to encode, но даже носители языка зачастую в них путаются. К тому же термин набор символов (character set или charset) используется в качестве синонима к термину кодированный набор символов (coded character set).
Всё это мы говорим к тому, что имеет смысл обращать внимание на контекст и различать ситуации, когда речь идёт о кодовой позиции абстрактного юникод-символа и когда речь идёт о его байтовом представлении.
Что такое Юникод?
Юникод (Unicode), это многоязычный, основанный на ASCII стандарт кодирования символов, а также, связанное с ним, семейство многобайтных кодировок. Если некоторые слова из предыдущего предложения вам не понятны, давайте рассмотрим их подробнее.
Что такое кодировка
Современные компьютеры всё ещё достаточно глупые и, в большинстве своём, не умеют работать ни с чем, кроме чисел. Мы рассматриваем на своих мониторах фотографии, смотрим фильмы, играем в игры. Но для компьютеров всё это лишь безликий поток нулей и единичек. Так же и текст — для компьютера это просто набор байтов. Буквы и любые другие символы представляются в машинной памяти, как числа.
Поэтому программистам при работе с текстом приходится делать подобные соглашения: «А давайте каждому символу будет соответствовать один байт. Причём, если в байте будет число 43, то будем считать, что это цифра ноль. А если число 66, то пусть это будет заглавная латинская буква B».
Подобный список всех используемых символов и соответствующих им чисел и называется кодировкой. Вы, скорее всего, уже слышали названия многих кодировок: Windows-1251, KOI-8, ну и, конечно, Unicode.
Крякозябры
Наверное, часто бывала ситуация, когда вы открываете страницу в браузере, а там вместо текста какая-то мешанина из чудных символов. Или просто сплошные вопросительные знаки. Или вы пишете любовное письмо своей девушке, а она звонит вам и говорит «что за нечитаемый бред ты мне прислал? Я обиделась».
Это всё из-за того, что в мире наплодилось слишком много разных кодировок. И текст в одной из них выглядит совершенно не так, как в другой. Дело в том, что компьютер не знает какую кодировку вы используете для текста. Для него это просто последовательность каких-то чисел.
Например, ваш текстовый редактор настроен на кодировку Windows-1251. И вы пишете «Здравствуйте, дорогая Маша!». Вы нажимаете первую букву и программа думает: «ага, русская заглавная буква Зэ — код 199». И записывает число 199 в файл. Маша получает ваше письмо, но в её почтовом клиенте стоит кодировка KOI8-R (потому что Маша любит старый Unix). А в этой кодировке числу 199 соответствует строчная буква «г». И Маша читает: «гДПЮБЯРБСИРЕ, ДНПНЦЮЪ лЮЬЮ!». Маша обиделась!
Чтобы подобного не происходило, нужно каким-то образом указывать кодировку в которой набран текст. Например, в HTML это делается с помощью тега:
ASCII
В определённый момент времени распространение получила кодировка ASCII (American Standard Code for Information Interchange). В ней определены 128 символов с кодами от 0 до 127. Сюда включён латинский алфавит, цифры и основные знаки препинания (
Практически все современные кодировки, использующиеся на персональных компьютерах являются ASCII-совместимыми. То есть первые 128 символов у них кодируются одинаково, а различия начинаются с кода 128 и выше. Вышеупомянутые Windows-1251 и KOI8-r также основаны на ASCII и если бы письмо начиналось бы с «Hello, my dear Maria!», то недопонимания не возникло бы.
Основан на ASCII и Юникод.
Однобайтные кодировки
Одна из причин, по которой появилось такое большое количество кодировок, это то, что вначале каждая компания придумывала свои стандарты, не обращая внимания на другие. Вторая причина заключается в том, что старые кодировки были однобайтными. То есть каждому символу в тексте соответствует один байт в памяти компьютера.
Однобайтные кодировки всем хороши: они компактны, с ними легко работать (нужно достать пятый символ — просто берём пятый байт от начала). Единственная проблема: в них помещается мало символов. Ровно столько, сколько значений может принимать один байт, то есть обычно, это 256. Например, в Windows-1251 мы отдали 128 символов под ASCII, добавили 66 букв русского алфавита (строчные и заглавные), несколько знаков препинания и вот у нас уже остаётся не так много свободных позиций. Даже на псевдографику не хватает.
То есть свести в одну кодировку все возможные символы даже европейских алфавитов достаточно сложно. А уж для китайцев с их тысячами иероглифов вообще всё тоскливо. А о всяких смайликах, эмоджи и иконках самолётиков и думать нечего. Поэтому для кириллицы приходилось изобретать свою кодировку, а для греческого языка другую.
Впрочем, такая ситуация сохранялась достаточно долго. Потому что проблемы англоязычных пользователей и программистов решила ASCII, а до китайских проблем им не было дела. С ростом же глобального интернета вдруг оказалось, что в мире говорят не только на английском языке, поэтому с кодировками нужно что-то менять.
Многобайтные кодировки
Самым простым решением было взять два байта вместо одного. Плюс такого решения: теперь можно в рамках одной кодировки использовать 65 тысяч символов. Минусы тоже есть:
Стандарт Unicode
В конечном итоге всё вылилось в стандарт Юникода, который худо-бедно, но решает практически все стоявшие перед кодировками проблемы.
С одной стороны, Юникод позволяет кодировать практически неограниченное количество символов. В последнем стандарте определено более 100 000 различных символов всех современных и многих уже мёртвых языков, а также различные иконки и пиктограммы. С другой стороны, некоторые способы кодирования позволяют Юникоду оставаться ASCII-совместимыми. Что позволяет работать, как и раньше многим программам, а также американским и другим англоязычным пользователям, многие из которых появления Юникода даже не заметили. В Юникоде также собраны все символы из всех популярных стандартов кодирования, что позволяет преобразовать в него любой текст из старой кодировки.
Практически все современные программы, работающие с текстом, понимают Юникод. Более того, обычно они в нём и работают. Например, даже когда вы открываете сайт в старой доброй Windows-1251, браузер сначала внутри у себя перекодирует все тексты в Юникод, а потом отображает их. В общем, Юникод, это светлое будущее интернета и всей компьютерной индустрии.
Отличие набора символов от кодировки
Термины «кодировка», «стандарт кодирования», «набор символов» обычно используются, как синонимы, но между ними есть и тонкие различия. Важно понимать разницу между «стандартом» и, собственно, «кодировкой». Некий стандарт просто говорит, что буква «A», это число 65, а буква «B» — 66. Кодировка же отвечает за то, как эти числа представить в памяти компьютера.
То есть в стандарте Юникода определено, что кириллической букве «А» соответствует абстрактное число 1040. Как представить это число в виде последовательности байтов решает уже конкретная кодировка — UTF-8, UTF-16, UTF-32.
То есть текстовый файл не может быть в кодировке «Юникод», а только в конкретной кодировке «UTF-8» или «UTF-16».
Кодировки и шрифты
Юникод, как и любая другая кодировка не описывает того, как следует отрисовывать символы. Для него число 1040, это «кириллическая заглавная буква А». А какая она, печатная, прописная, наклонная, жирная или с завитушками, это не его дело.
За изображение символа отвечают шрифты. Поэтому один и тот же символ в разных шрифтах может выглядеть по разному, а то и вообще отсутствовать.
© Таблица символов Юникода, 2012–2021.
Юникод® — это зарегистрированная торговая марка консорциума Юникод в США и других странах. Этот сайт никак не связан с консорциумом Юникод. Официальный сайт Юникода располагается по адресу www.unicode.org.
Мы используем 🍪cookie, чтобы сделать сайт максимально удобным для вас. Подробнее
Юникод для чайников
Сам я не очень люблю заголовки вроде «Покемоны в собственном соку для чайников\кастрюль\сковородок», но это кажется именно тот случай — говорить будем о базовых вещах, работа с которыми довольно часто приводить к купе набитых шишек и уйме потерянного времени вокруг вопроса — «Почему же оно не работает?». Если вы до сих пор боитесь и\или не понимаете Юникода — прошу под кат.
Зачем?
Главный вопрос новичка, который встречается с впечатляющим количеством кодировок и на первый взгляд запутанными механизмами работы с ними (например, в Python 2.x). Краткий ответ — потому что так сложилось 🙂
Кодировкой, кто не знает, называют способ представления в памяти компьютера (читай — в нулях-единицах\числах) цифр, буков и всех остальных знаков. Например, пробел представляется как 0b100000 (в двоичной), 32 (в десятичной) или 0x20 (в шестнадцатеричной системе счисления).
Так вот, когда-то памяти было совсем немного и всем компьютерам было достаточно 7 бит для представления всех нужных символов (цифры, строчный\прописной латинский алфавит, куча знаков и так называемые управляемые символы — все возможные 127 номеров были кому-то отданы). Кодировка в это время была одна — ASCII. Шло время, все были счастливы, а кто не был счастлив (читай — кому не хватало знака «©» или родной буквы «щ») — использовали оставшиеся 128 знаков на свое усмотрение, то есть создавали новые кодировки. Так появились и ISO-8859-1, и наши (то есть кириличные) cp1251 и KOI8. Вместе с ними появилась и проблема интерпретации байтов типа 0b1******* (то есть символов\чисел от 128 и до 255) — например, 0b11011111 в кодировке cp1251 это наша родная «Я», в тоже время в кодировке ISO-8859-1 это греческая немецкая Eszett (подсказывает Moonrise) «ß». Ожидаемо, сетевая коммуникация и просто обмен файлами между разными компьютерами превратились в чёрт-знает-что, несмотря на то, что заголовки типа ‘Content-Encoding’ в HTTP протоколе, email-письмах и HTML-страницах немного спасали ситуацию.
В этот момент собрались светлые умы и предложили новый стандарт — Unicode. Это именно стандарт, а не кодировка — сам по себе Юникод не определяет, как символы будут сохранятся на жестком диске или передаваться по сети. Он лишь определяет связь между символом и некоторым числом, а формат, согласно с которым эти числа будут превращаться в байты, определяется Юникод-кодировками (например, UTF-8 или UTF-16). На данный момент в Юникод-стандарте есть немного более 100 тысяч символов, тогда как UTF-16 позволяет поддерживать более одного миллиона (UTF-8 — и того больше).
Ближе к делу!
Естественно, есть поддержка Юникода и в Пайтоне. Но, к сожалению, только в Python 3 все строки стали юникодом, и новичкам приходиться убиваться об ошибки типа:
Давайте разберемся, но по порядку.
Зачем кто-то использует Юникод?
Почему мой любимый html-парсер возвращает Юникод? Пусть возвращает обычную строку, а я там уже с ней разберусь! Верно? Не совсем. Хотя каждый из существующих в Юникоде символов и можно (наверное) представить в некоторой однобайтовой кодировке (ISO-8859-1, cp1251 и другие называют однобайтовыми, поскольку любой символ они кодируют ровно в один байт), но что делать если в строке должны быть символы с разных кодировок? Присваивать отдельную кодировку каждому символу? Нет, конечно, надо использовать Юникод.
Зачем нам новый тип «unicode»?
Вот мы и добрались до самого интересного. Что такое строка в Python 2.x? Это просто байты. Просто бинарные данные, которые могут быть чем-угодно. На самом деле, когда мы пишем что-нибудь вроде:интерпретатор не создает переменную, которая содержит первые четыре буквы латинского алфавита, но только последовательность с четырёх байт, и латинские буквы здесь используются исключительно для обозначения именно этого значения байта. То есть ‘a’ здесь просто синоним для написания ‘\x61’, и ни чуточку больше. Например:
И ответ на вопрос — зачем нам «unicode» уже более очевиден — нужен тип, который будет представятся символами, а не байтами.
Хорошо, я понял чем есть строка. Тогда что такое Юникод в Пайтоне?
«type unicode» — это прежде всего абстракция, которая реализует идею Юникода (набор символов и связанных с ними чисел). Объект типа «unicode» — это уже не последовательность байт, но последовательность собственно символов без какого либо представления о том, как эти символы эффективно сохранить в памяти компьютера. Если хотите — это более высокой уровень абстракции, чем байтовый строки (именно так в Python 3 называют обычные строки, которые используются в Python 2.6).
Как пользоваться Юникодом?
Как из юникод-строки получить обычную? Закодировать её:
Алгоритм кодирования естественно обратный приведенному выше.
Не кодируется 🙁
Разберем примеры с начала статьи. Как работает конкатенация строки и юникод-строки? Простая строка должна быть превращена в юникод-строку, и поскольку интерпретатор не знает кодировки, от использует кодировку по умолчанию — ascii. Если этой кодировке не удастся декодировать строку, получим некрасивую ошибку. В таком случае нам нужно самим привести строку к юникод-строке, используя правильную кодировку:
«UnicodeDecodeError» обычно есть свидетельством того, что нужно декодировать строку в юникод, используя правильную кодировку.
Теперь использование «str» и юникод-строк. Не используйте «str» и юникод строки 🙂 В «str» нет возможности указать кодировку, соответственно кодировка по умолчанию будет использоваться всегда и любые символы > 128 будут приводить к ошибке. Используйте метод «encode»:
«UnicodeEncodeError» — знак того, что нам нужно указать правильную кодировку во время превращения юникод-строки в обычную (или использовать второй параметр ‘ignore’\’replace’\’xmlcharrefreplace’ в методе «encode»).
Хочу ещё!
Хорошо, используем бабу-ягу из примера выше ещё раз:
Есть ещё способ использования «u»» для представления, например, кириллицы, и при этом не указывать кодировку или нечитабельные юникод-поинты (то есть «u’\u1234’»). Способ не совсем удобный, но интересный — использовать unicode entity codes:
Ну и вроде всё. Основные советы — не путать «encode»\«decode» и понимать различия между байтами и символами.
Python 3
Здесь без кода, ибо опыта нет. Свидетели утверждают, что там всё значительно проще и веселее. Кто возьмется на кошках продемонстрировать различия между здесь (Python 2.x) и там (Python 3.x) — респект и уважуха.
Полезно
Раз уж мы о кодировках, порекомендую ресурс, который время-от-времени помогает побороть кракозябры — http://2cyr.com/decode/?lang=ru.
Unicode HOWTO — официальный документ о том где, как и зачем Юникод в Python 2.x.
Спасибо за внимание. Буду благодарен за замечания в приват.
Что такое unicode в чем достоинства и недостатки использования этого семейства кодировок
Unicode и кодировки UTF-8, UTF-16. Основная информация, необходимая для разработки ПО.
История и введение
При разработке программы, где одной из главных составляющих является задача обработки текста (ввод, редактирование, чтение), важно понимание системы unicode. По своей природе, компьютеры на самом простом уровне работают с битами, но человеку необходимы более сложные сущности, такие как: числа, буквы и другие символы. Поэтому, для их представления в компьютерных системах используются специальные кодировки, способные преобразовывать числовые коды в символы. Кодирование, в свою очередь, – это представление какой-либо информации в ином, закодированном виде, не репрезентативном для человека, который можно вернуть в исходное состояние. Кодировка – это набор правил, описывающий способ перевода одного представления информации в другое.
До появления стандарта unicode, в мире было множество разных схем кодирования символов. Однако, у них было значительное количество недостатков: не было универсальной кодировки — многие схемы были несовместимы по отношению друг к другу; они не описывали все необходимые пользователю символы (наличие множества языков). Для любого компьютера, особенно сервера, необходимо было поддерживать несколько кодировок, но даже при этом существовал риск того, что данные, при передаче, могли оказаться поврежденными.
Одна из наиболее популярных кодировок в тот момент это — ASCII. Всего в систему ASCII включены 128 символов — она использует все комбинации семи битов (от 0000000 до 1111111). Например, последовательность бит двоичной системы 01000001 в изложенной кодировке кодирует символ “A”, или 01000010 кодирует символ “B”. Основная проблема ASCII заключалась в том, что эта кодировка неплохо работала только в англоязычной среде. Использовать её с другими языками было затруднительно.
С целью разрешить несистематизированность и попытаться унифицировать систему кодирования символов, которая могла бы использоваться для множества языков, в 1991 году некоммерческой организацией «Консорциум Юникода» был разработан и предложен стандарт Unicode. В этот консорциум входят такие лидеры компьютерной индустрии, как Apple, HP, IBM, Microsoft, Oracle и т.д. поэтому, эта схема кодирования используется почти во всех современных технологиях и стандартах, а консорциум ставит своими задачами — развитие и распространение этой системы. В результате использования этого стандарта, можно значительно сэкономить на поддержке программного обеспечения. Главной особенностью Unicode является то, что она присваивает уникальный код любому символу, независимо от платформы, программы или языка.
Начиная с октября 1991 года, стандарт постоянно развивается. Последняя версия (12.1) вышла в мае 2019 года. В каждой новой версии появляется больше символов, иероглифов из восточных языков, эмодзи и различных письменностей.
Стандарт состоит из двух основных частей: универсального набора символов (англ. Universal character set, UCS) и семейства кодировок (англ. Unicode transformation format, UTF). Универсальный набор символов перечисляет допустимые по стандарту Unicode символы и присваивает каждому символу код в виде неотрицательного целого числа, записываемого обычно в шестнадцатеричной форме с префиксом U+, например, U+040F. Семейство кодировок определяет способы преобразования кодов символов для передачи в потоке или в файле.
Общее количество кодовых позиций в unicode — 1 112 064, несмотря на значительно больший объём возможных вариантов (2^31 = 2 147 483 648). Это было сделано для совместимости разных представлений: UTF-8, UTF-16 и UTF-32. В последней версии Unicode, за май 2019, количество всех символов составляет 137 994.
Важно отметить, что стандарт unicode полностью совместим основным предшественником — ASCII. Есть конкретная область с кодами от U+0000 до U+007F, которая содержит символы набора ASCII, соответствующие их кодами в ASCII.
Простыми словами, Unicode – это просто огромная таблица соответствия символов и чисел, а различные UTF кодировки определяют, как эти числа переводятся в биты.
Формы представления unicode: UTF-8 и UTF-16
Стандарт Unicode в настоящее время реализуется различными кодировками, самые распространённые — UTF-8 и UTF-16, кодировки с переменной длиной кодирования.
То что кодировка переменной длины, значит, что один символ может быть закодирован разным количеством структурных единиц кодировки, то есть разным количеством байт. Если символ может быть закодирован одним байтом (потому что номер пункта символа очень маленький), UTF-8 закодирует его одним байтом. Если нужно 2 байта, то используется 2 байта. Так, например, латиница кодируется одним байтом, а кириллица двумя байтами. Кодировка сообщает старшим битам, сколькими битами кодируется текущий символ. Такой способ экономит место, но так же, тратит его в случае, если эти сигнальные биты часто используются. Пример кодирования:
UTF-16 является компромиссом в использовании: все символы как минимум двухбайтные, но их размер может увеличиваться до 4 байт. На западных языках обычно эффективнее всего кодируются с использованием стандарта UTF-8 (так как большинство символов в таких текстах могут быть представлены в виде кодов размером в 1 байт). Если же говорить о восточных языках, то можно сказать, что файлы, хранящие тексты, написанные на этих языках, обычно получаются меньше при использовании UTF-16.
Любой символ юникода в кодировке UTF-16 может быть закодирован либо одной парой байт или кодовой парой, либо двумя.
Количество символов, кодируемых одной кодовой парой может быть 65 535 (2^16), что полностью совпадает с базовым блоком юникода. Все символы находящиеся в этом блоке в кодировке UTF-16 будут закодированы одной кодовой парой (двумя байтами), тут все просто.
Для символов, находящихся за пределами базового unicode диапазона потребуется уже две кодовые пары (4 байта). И механизм их кодирования немного сложнее.
Для пояснения, необходимо ввести понятие “суррогатной пары”. Суррогатная пара — это две кодовые пары используемые для кодирования одного символа (итого 4 байта). Для таких суррогатных пар в таблице юникода отведен специальный диапазон от D800 до DFFF. Это значит, что при преобразовании кодовой пары из байтового вида в шестнадцатеричный вы получаете число из этого диапазона, то перед вами не самостоятельный символ, а суррогатная пара.
Чтобы закодировать символ из диапазона 10000 — 10FFFF (то есть символ для которого нужно использовать более одной кодовой пары) нужно:
(Подробнее в дополнительной литературе: Как работают кодировки текста. Откуда появляются «кракозябры». Принципы кодирования. Обобщение и детальный разбор).
Иногда, при обработке текста, можно встретить проблему того, что символы, выглядящие для человека одинаково, имеют различное внутреннее представление. Например, букву é в можно представить двумя способами:
Таким образом, визуально буква будет выглядеть идентично, но закодирована по-разному. Это может быть серьезной проблемой, при написании ПО, потому что она может возникнуть, например, при вводе пользователем каких либо данных: аутентификация в системе — пользователь ввёл верные данные для входа, однако, при поиске записей в БД, запись может быть не найдена, потому что коды символов различны. Для решения этой проблемы существуют алгоритмы нормализации строк (приведение их к каноническому виду).
Существуют четыре стандартных формы (алгоритма) нормализации:
Чаще всего используется форма нормализации NFC. При использовании этого алгоритма все символы сначала подвергаются декомпозиции (процесс, при котором все буквы по возможности разбиваются на модификаторы), после чего все комбинирующиеся последовательности подвергаются повторной композиции (процесс, при котором все буквы по возможности объединяются в одну) в порядке, определяемом стандартом. Для практического применения можно выбрать любую форму. Главное — применять её последовательно. В результате поступление на вход программы одних и тех же данных всегда будет приводить к одному и тому же результату.
Несмотря на наличие такого унифицированного стандарта кодирования символов, у Unicode остаются некоторые нерешенные проблемы:
Некоторые недостатки связаны не с самим Unicode, а с возможностями обработчиков текста.
Unicode — достаточно сложная система, в которой существует множество хитростей и лазеек: от невидимых символов и контрольных знаков до суррогатных пар и комбинированных эмодзи (когда при сложении двух знаков получается третий). По этой причине, злоумышленники, хорошо осведомленные о нюансах Unicode могут использовать их для атаки ПО.
Например, существует атака, описанная OWASP, направленная на поиск недостатков в механизме декодирования, реализованном в приложениях при декодировании формата данных Unicode. Злоумышленник может использовать эту технику для кодирования определенных символов в URL-адресе, чтобы обойти фильтры приложений, таким образом получая доступ к ограниченным ресурсам на веб-сервере или принудительно просматривая защищенные страницы.
Другой пример уязвимости, найденной сравнительно недавно, это брешь в процедуре восстановления забытого пароля на GitHub, которая позволяла злоумышленнику получить пароль от чужого аккаунта.
В рамках этой процедуры восстановления, выполнялось сравнение введённого адреса электронной почты с адресом, который хранится в базе. Алгоритм проверки:
Связанные с Юникодом баги имеют такое свойство, что их можно встретить в любом приложении, которое обрабатывает текст, введённый пользователем. Уязвимости есть и в веб-приложениях, и в нативных программах под Android и iOS.
Большую известность получила ошибка, связанная с воспроизведением символов Unicode для индийского языка телугу. Проблема возникала на некоторых версиях iOS в приложениях, использующих дефолтный шрифт San Francisсo. Получив всего несколько символов జ్ఞా, пользователь терял управление над многими приложениями в iOS, включая почту и Facebook. Если один из символов телугу появлялся во всплывающих уведомлениях, то блокировался SpringBoard — приложение, отвечающее за главный экран в iOS.
Ошибка изменения символов, приводящая к краху системы, кроется в особенности языка телугу, бенгали и некоторых других диалектов. Она заключается в последовательном построении иероглифов из элементов письма — глифов, при этом имеется определенное расположение символов, не характерное для языка. К сбою приводит преобразование соединительных суффиксов в согласных: когда вторую согласную букву слоговой алфавитной письменности телугу присоединяют к первой согласной для объединения без значительного изменения формы слова. Вследствие несовместимости глифов возникает ошибка, которую не может обработать процессор устройства.
Подводя итог, можно отметить следующие важные моменты касательно Unicode: