company_banner

Символы Unicode: о чём должен знать каждый разработчик

Автор оригинала: Kealan Parr
  • Перевод


Если вы пишете международное приложение, использующее несколько языков, то вам нужно кое-что знать о кодировке. Она отвечает за то, как текст отображается на экране. Я вкратце расскажу об истории кодировки и о её стандартизации, а затем мы поговорим о её использовании. Затронем немного и теорию информатики.

Введение в кодировку


Компьютеры понимают лишь двоичные числа — нули и единицы, это их язык. Больше ничего. Одно число называется байтом, каждый байт состоит из восьми битов. То есть восемь нулей и единиц составляют один байт. Внутри компьютеров всё сводится к двоичности — языки программирования, движений мыши, нажатия клавиш и все слова на экране. Но если статья, которую вы читаете, раньше была набором нулей и единиц, то как двоичные числа превратились в текст? Давайте разберёмся.

Краткая история кодировки


На заре своего развития интернет был исключительно англоязычным. Его авторам и пользователям не нужно было заботиться о символах других языков, и все нужды полностью покрывала кодировка American Standard Code for Information Interchange (ASCII).

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

01001000 01100101 01101100 01101100 01101111 00100000 01110111 01101111 01110010 01101100 01100100

то с помощью ASCII он преобразует её во фразу «Hello world».

Один байт (восемь бит) был достаточно велик, чтобы вместить в себя любую англоязычную букву, как и управляющие символы, часть из которых использовалась телепринтерами, так что в те годы они были полезны (сегодня уже не особо). К управляющим символам относился, например 7 (0111 в двоичном представлении), который заставлял компьютер издавать сигнал; 8 (1000 в двоичном представлении) — выводил последний напечатанный символ; или 12 (1100 в двоичном представлении) — стирал весь написанный на видеотерминале текст.

В те времена компьютеры считали 8 бит за один байт (так было не всегда), так что проблем не возникало. Мы могли хранить все управляющие символы, все числа и англоязычные буквы, и даже ещё оставалось место, поскольку один байт может кодировать 255 символов, а для ASCII нужно только 127. То есть неиспользованными оставалось ещё 128 позиций в кодировке.

Вот как выглядит таблица ASCII. Двоичными числами кодируются все строчные и прописные буквы от A до Z и числа от 0 до 9. Первые 32 позиции отведены для непечатаемых управляющих символов.


Проблемы с ASCII


Позиции со 128 по 255 были пустыми. Общественность задумалась, чем их заполнить. Но у всех были разные идеи. Американский национальный институт стандартов (American National Standards Institute, ANSI) формулирует стандарты для разных отраслей. Там утвердили позиции ASCII с 0 по 127. Их никто не оспаривал. Проблема была с остальными позициями.

Вот чем были заполнены позиции 128-255 в первых компьютерах IBM:


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

Все эти различные концовки назвали кодовыми страницами.

Что такое кодовые страницы ASCII?


Здесь собрана коллекция из более чем 465 разных кодовых страниц! Существовали разные страницы даже в рамках какого-то одного языка, например, для греческого и китайского. Как можно было стандартизировать этот бардак? Или хотя бы заставить его работать между разными языками? Или между разными кодовыми страницами для одного языка? В языках, отличающихся от английского? У китайцев больше 100 000 иероглифов. ASCII даже не может всех их вместить, даже если бы решили отдать все пустые позиции под китайские символы.

Эта проблема даже получила название Mojibake (бнопня, кракозябры). Так говорят про искажённый текст, который получается при использовании некорректной кодировки. В переводе с японского mojibake означает «преобразование символов».


Пример бнопни (кракозябров).

Безумие какое-то...


Именно! Не было ни единого шанса надёжно преобразовывать данные. Интернет — это лишь монструозное соединение компьютеров по всему миру. Представьте, что все страны решили использовать собственные стандарты. Например, греческие компьютеры принимают только греческий язык, а английские отправляют только английский. Это как кричать в пустой пещере, тебя никто не услышит.

ASCII уже не удовлетворял жизненным требованиям. Для всемирного интернета нужно было создать что-то другое, либо пришлось бы иметь дело с сотнями кодовых страниц.

��� Если только ������ вы не хотели ��� бы ��� читать подобные параграфы. �֎֏0590֐��׀ׁׂ׃ׅׄ׆ׇ

Так появился Unicode


Unicode расшифровывают как Universal Coded Character Set (UCS), и у него есть официальное обозначение ISO/IEC 10646. Но обычно все используют название Unicode.

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

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

«Hello World»

U+0048 : латинская прописная H
U+0065 : латинская строчная E
U+006C : латинская строчная L
U+006C : латинская строчная L
U+006F : латинская строчная O
U+0020 : пробел
U+0057 : латинская прописная W
U+006F : латинская строчная O
U+0072 : латинская строчная R
U+006C : латинская строчная L
U+0064 : латинская строчная D

Префикс U+ говорит о том, что это стандарт Unicode, а число — это результат преобразования двоичных чисел. Стандарт использует шестнадцатеричную нотацию, которая является упрощённым представлением двоичных чисел. Здесь вы можете ввести в поле что угодно и посмотреть, как это будет преобразовано в Unicode. А здесь можно полюбоваться на все 143 859 кодовых пунктов.

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

Осталось добавить последний ингредиент.

Unicode Transform Protocol (UTF)


UTF — протокол кодирования кодовых пунктов в Unicode. Он прописан в стандарте и позволяет кодировать любой кодовый пункт. Однако существуют разные типы UTF. Они различаются количеством байтов, используемых для кодировки одного пункта. В UTF-8 используется один байт на пункт, в UTF-16 — два байта, в UTF-32 — четыре байта.

Но если у нас есть три разные кодировки, то как узнать, какая из них применяется в конкретном файле? Для этого используют маркер последовательности байтов (Byte Order Mark, BOM), который ещё называют сигнатурой кодировки (Encoding Signature). BOM — это двухбайтный маркер в начале файл, который говорит о том, какая именно кодировка тут применена.

В интернете чаще всего используют UTF-8, она также прописана как предпочтительная в стандарте HTML5, так что уделю ей больше всего внимания.


Этот график построен в 2012-м, UTF-8 становилась доминирующей кодировкой. И всё ещё ею является.


График показывает распространённость UTF-8.

Что такое UTF-8 и как она работает?


UTF-8 кодирует с помощью одного байта каждый кодовый пункт Unicode с 0 по 127 (как в ASCII). То есть если вы писали программу с использованием ASCII, а ваши пользователи применяют UTF-8, они не заметят ничего необычного. Всё будет работать как задумано. Обратите внимание, как это важно. Нам нужно было сохранить обратную совместимость с ASCII в ходе массового внедрения UTF-8. И эта кодировка ничего не ломает.

Как следует из названия, кодовый пункт состоит из 8 битов (один байт). В Unicode есть символы, которые занимают несколько байтов (вплоть до 6). Это называют переменной длиной. В разных языках удельное количество байтов разное. В английском — 1, европейские языки (с латинским алфавитом), иврит и арабский представлены с помощью двух байтов на кодовый пункт. Для китайского, японского, корейского и других азиатских языков используют по три байта.

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

И теперь мы, как по волшебству, пришли к соглашению, как закодировать шумерскую клинопись (Хабр её не отображает), а также значки emoji!

Подытожив сказанное: сначала читаем BOM, чтобы определить версию кодировки, затем преобразуем файл в кодовые пункты Unicode, а потом выводим на экран символы из набора Unicode.

Напоследок про UTF


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

Как нам задавать кодировку? Поскольку HTML пишется на английском, и почти все кодировки прекрасно работают с английским, мы можем указать кодировку в начале раздела <hеad>.

<html lang="en">
<head>
  <meta charset="utf-8">
</head>

Важно сделать это в самом начале <hеad>, поскольку парсинг HTML может начаться заново, если в данный момент используется неправильная кодировка. Также узнать версию кодировки можно из заголовка Content-Type HTTP-запроса/ответа.

Если HTML-документ не содержит упоминания кодировки, спецификация HTML5 предлагает такое интересное решение, как BOM-сниффинг. С его помощью мы по маркеру порядка байтов (BOM) можем определить используемую кодировку.

Это всё?


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

Недавно я прочитал об очень интересном баге, связанном с некорректным отображением в Twitter русских Unicode-символов.

Если вы дочитали до конца, то вы молодцы. Предлагаю сделать домашнюю работу. Посмотрите, как могут ломаться сайты при использовании неправильной кодировки. Я воспользовался этим расширением для Google Chrome, поменял кодировку и попытался открывать разные страницы. Информация была совершенно нечитаемой. Попробуйте сами, как выглядит бнопня. Это поможет понять, насколько важна кодировка.


Заключение


При написании этой статьи я узнал о Майкле Эверсоне. С 1993 года он предложил больше 200 изменений в Unicode, добавил в стандарт тысячи символов. По состоянию на 2003 год он считался самым продуктивным участником. Он один очень сильно повлиял на облик Unicode. Майкл — один из тех, кто сделал интернет таким, каким мы его сегодня знаем. Очень впечатляет.

Надеюсь, мне удалось показать вам, для чего нужны кодировки, какие проблемы они решают, и что происходит при их сбоях.
Mail.ru Group
Строим Интернет

Комментарии 28

    +10
    Одно число называется битом, каждый бит состоит из восьми байтов.

    что-то здесь не так…
      0
      Спасибо, поправил
      +5

      В статье не хватает этой фразы


      Unicode это набор символов, UTF — это кодировка
        +4

        Да! Это и есть то, о чем должен знать каждый разработчик.


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


        Если у вас UTF-строка, то значит у вас сырые баты. Вы не можете посчитать количество слов или символов, искать подстроку или разделять на части, не зная кодировку этой конкретной строки.

        0
        Как следует из названия, кодовый пункт состоит из 8 битов (один байт). В Unicode есть символы, которые занимают несколько байтов (вплоть до 6). Это называют переменной длиной. В разных языках удельное количество байтов разное.
        Если нужно, чтобы символ занимал больше одного байта, то применяется битовая комбинация, обозначающая переход — он говорит о том, что символ продолжается в нескольких следующих байтах.

        Не очень понятно как это реализовано, что за битовая комбинация, можно пример?
        +1
        Спасибо за статью. Меня всегда интересовало, почему на некоторых ресурсах которые представляют юникод символы или же эмодзи (например этот) не все символы показывает (просто пустой прямоугольник), хотя в теории они же должны быть, это от настроек операционной системы зависит, или как? С обновление винды, должны появляться новые символы?
          +2

          Это обычно зависит от самого шрифта. Если он базируется на Arial, то в некоторых его сборках нету половины юникода.
          Поэтому если в шрифте просто нету символа, то там прямоугольник

            0

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

              +2
              А есть еще просто пустые места, в которых просто нет символов. Юникод еще не заполнен на 100%.
            0
            Здесь нельзя не написать про то, как BOM-символы постоянно ломают сайты тем, кто правит файлы в виндовых редакторах. Из-за того, что оказываются перед хедерами.
              0
              В Unicode есть символы, которые занимают несколько байтов (вплоть до 6)


              UTF-8 кодирует с помощью одного байта каждый кодовый пункт Unicode с 0 по 127 (как в ASCII).


              идея не раскрыта
                +1
                кодовых пунктов (кодовых точек)

                Я действительно понимаю всю сложность, с которой столкнулся переводчик, но лучше было оставить английский вариант code point чем так переводить.

                  +1
                  Интересно бывают ли в природе шрифты, в которых есть начертания всех 143 859 кодовых пунктов? (Кстати, эта важная часть в статье никак не раскрывается) Также интересно, сколько может весить такой файлик…
                    +1
                    Что значит “в природе” и что значит “бывают”? TrueType разрабытывался во времяна Unicode 2.0, потому в одном TrueType шрифте бывает максимум 65535 глифов. Но OpenType позволет собрать в одну коллекцию несколько TrueType шрифтов.
                    И вообще: работа со шрифтами — это сильно отдельная вещь, даже небольшая часть описания этого всего — даёт статью заметно большую обсуждаемой.
                    А ведь потом из всего этого ещё и строку нужно сложить.

                    Всё это, к сожалению не прихоти разработчиков стандартов, а отражение того факта, что люди за тысячелетия придумали много, много, вот реально много всякого-разного для передачи текста на бумаге.
                    +5
                    В UTF-8 используется один байт на пункт, в UTF-16 — два байта, в UTF-32 — четыре байта.

                    В UTF-8 используется от одного до четырёх байт на пункт, в UTF-16 — два или четыре байта, в UTF-32 — четыре байта.


                    В Unicode есть символы, которые занимают несколько байтов (вплоть до 6).

                    Не в Unicode, а в UTF-8. И не до 6, а до 4.


                    Статья очень низкого уровня, прочтение может только больше запутать.

                      –2
                      Не в Unicode, а в UTF-8. И не до 6, а до 4.

                      Это в мирное время. А вот прилетят инопланетяне со своим юникодом, как Земля будет договариваться об условиях своей капитуляции? То-то же.
                        0
                        Символ (единица перемещения курсора / выделения) может состоять из нескольких пунктов; хотя ограничение в 6 байтов — действительно непонятно откуда взялось.

                          0
                          хотя ограничение в 6 байтов — действительно непонятно откуда взялось

                          Ограничения формата кодирования. В UTF-8 по первому байту определяется количество следующих за них хвостовых байтов.


                          0xxxxxxx — значения 0x0000..0x007F
                          110xxxxx + 1 × 10xxxxxx — значения 0x0080..0x07FF
                          1110xxxx + 2 × 10xxxxxx — значения 0x0800..0xFFFF
                          11110xxx + 3 × 10xxxxxx — значения 0x10000..0x10FFFF


                          а дальше Юникод заканчивается и больше вариантов не нужно. (И если присмотреться, то не все биты используются для кодирования).


                          Хотя если продолжить приницип кодирования, то можно получить комбинации:


                          111110xx + 4 × 10xxxxxx
                          1111110x + 5 × 10xxxxxx


                          А дальше уже место в первом байте заканчивается — на комбинации из шести байтов (1 начальных, 5 хвостовых).


                          Правда, часто забывают, что остаётся ещё 11111110, к которому можно приклеить что вам хочется; например, шесть хвостовых байтов, чтобы получить комбинацию из семи «типа UTF-8».

                            0
                            Хотя если продолжить приницип кодирования, то можно получить комбинации

                            Это очевидно, но это не UTF-8, а разговор про UTF-8.

                          +1
                          Я думаю это была проблема того, что переводчик не понял что означает пункт и что означает длина всего символа.

                          Не в Unicode, а в UTF-8. И не до 6, а до 4.

                          До четырех урезали в 2003, до этого было до 6.
                            0

                            В оригинале то же самое:


                            Unicode is made up of lots of code points (mapping lots of characters from around the world to a key that all computers can reference.) A collection of code points is called a character set — which is what Unicode is.

                            But there are different types of UTF standards. They differ depending on the amount of bytes used to encode one code point. It also depends on whether you're using UTF-8 (one byte per code point), UTF-16 (two bytes per code point) or UTF-32 (four bytes per code point).
                          0

                          Оригинальная статья — это вольный пересказ древней статьи Спольски?

                            +1
                            Попробуйте сами, как выглядит бнопня. Это поможет понять, насколько важна кодировка.


                              +2

                              Те, кто знает не понаслышке, что такое бНОПНЯ вводят адрес транслитом, а имена файлов — без пробелов)

                              +1

                              Почему бы не раскрыть, откуда пошла бНОПНЯ, и кто такие KOI8-R и CP-1251?

                                0
                                Префикс U+ говорит о том, что это стандарт Unicode, а число — это результат преобразования двоичных чисел. Стандарт использует шестнадцатеричную нотацию, которая является упрощённым представлением двоичных чисел.


                                Почему двоичных? Любых чисел. Да и вообще, что такое «двоичное число»? Есть двоичная система, есть двоичные цифры, а вот сами числа вообще к системам счисления не привязаны.

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое