Про Юникод написано много, но мне кажется, что это одна из тем, про которую "чисто ради интереса" никто читать не полезет. Поэтому я решил поделиться здесь одной штукой, которая, как я в разговорах со многими замечал, не всем известна. Речь о том, "сколько байт занимает юникод".

Старые кодировки (например WINDOWS-1251, KOI8-R) совершенно четко определяют, какие конкретные значения одного байта используются для кодирования одного символа (человеческого или служебного). И отсюда вытекает их известная проблема с тем, что они не могут кодировать больше 256 символов. Для того, чтобы с этой проблемой бороться универсально, был придуман Юникод.

Вот дальше идет то самое частое, потому что очень логичное, заблуждение о том, что Юникод — это кодировка, где для каждого символа отводится 2 байта, а значит она может кодировать 65536 символов. Больше того, хоть я и не смотрел специально, кажется именно так она и была задумана в самом начале. Но сейчас все совсем не так.

Довольно быстро возникла необходимость кодировать гораздо больше, чем 65536 символов (чуть больше миллиона сейчас) и, что главное, появилась необходимость кодировать их по-разному. Поэтому в отличие от старых кодировок стандарт Юникода полностью разделяет две вещи:

Номера, присваиваемые символам, полностью абстрактны. Это не байты, не двубайты, а просто числа. Например символу "Заглавная кириллическая буква А" назначен номер 1040, он же — 410 в шестнадцатеричной системе. И формально в Юникоде это принято записывать как U+0410.

А вот способов представлять это число в памяти или записывать в файлы существует больше одного. Называются они "формы кодирования". Вот наиболее распространенные.

UTF'ы

Есть несколько форм кодирования, которые официально входят в стандарт.

UTF-8

В этой форме юникодные символы кодируются одиночными байтами. Но поскольку одного байта для кодирования миллиона символов слегка мало, разные символы кодируются разным количеством байтов. Те, которые входят в старый ASCII, кодируются одним байтом и их значения полностью с ASCII совпадают. Русские и, например, западноевропейские символы кодируются двумя байтами, японские катакана и хирагана — тремя, а есть еще всякая экзотика, где могут быть и четыре байта.

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

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

UTF-16 (или, что почти одно и то же — UCS-2)

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

Кстати, UCS-2 — это как раз UTF-16 без этих дополнительных символов, то есть строго символ = 2 байта.

На практике редкие символы действительно редки (часто встречались с древнегреческой музыкальной грамотой?), поэтому во многих системах для внутреннего представления символов используется именно UTF-16, например в NTFS для имен файлов, в Delphi для WideString'ов, и Java, насколько я знаю, тоже внутри вся в UTF-16 работает со строками.

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

UTF-32 (или, что почти одно и то же — UCS-4)

Это — форма для перфекционистов. Для представления символа используется строго 4 байта, которыми можно представить абсолютно любой юникодный символ. С недавнего времени тот же Python на большинстве платформ использует именно четырехбайтовое представление для юникодных строк.

Отличия от UCS-4 совсем умозрительные и непрактические.

Минус у этого представления, помимо плохой переносимости, как у UTF-16, еще и в том, что UTF-32 попросту занимает еще больше места.

Другие формы

Помимо UTF'ов, внесенных в стандарт Юникода, существует еще несколько знакомых многим способов представления юникодных номеров.

В первую очередь это XML и HTML со своими А и А. Такое представление, хоть и занимает места еще больше, чем UTF-32 (по 7 байт в двух предыдущих примерах), зато обладает несомненным плюсом в том смысле, что их можно использовать, даже если вы храните сам файл в старой однобайтовой кодировке. А вот если у вас файл и так хранится в одном из юникодных представлений (UTF-8 чаще всего), то в него можно вставлять все стрелочки и кавычечки прямо в UTF-8, которые можно либо взять из charmap'а, либо сам редактор может уметь обрабатывать всякие мудреные комбинации клавиш для этого. Оно и выглядит более читаемо при этом.

Ну и еще напоследок — представление в Javascript. Суть та же, что и у (X|HT)ML'а, только запись другая: \u0410, \u0160...


То, что я тут написал — довольно упрощенное прдставление, где я не касался всяких форм нормализации, символов, которые не символы, и прочих "высших и низших суррогатов". Хотя вчера успел совершенно замучить жену цитатами из стандарта Юникода, который читал, чтобы уточнить пару вещей для статьи. Штука, конечно, интересная, но удивительно запутанная. Если кого это все заинтересует, то лучше всего начать со статьи про Юникод в русской Wikipedia (очень хорошая).

Комментарии: 9

  1. BOLK

    Есть ещё UTF-7, а у UTF-8 есть интересная особенность - всегда можно определить находимся ли мы посередине символа или в его начале.

    Ну и ещё ты забыл упомянуть, что наличие нулевого байта в строке это проблема языков, где строки хранятся как в Cи.

  2. Alexander

    Спасибо, наконец-то я получил толковое объяснение почему так много кодировок называющих себя Юникодом. ))

  3. Pak

    Замечательный обзор, спасибо.
    Зная, что Вы, Иван, знаете Python не понаслышке, хочу спросить о следующем:

    В первую очередь это XML и HTML со своими А;

    Есть ли библиотечка, которая переводит все эти
    привет питон
    в "нормальный" вид, а вот как возник вопрос:

    from mechanize import *
    from BeautifulSoup import *
    b = Browser()
    b.open("http://www.translate.ru/text.asp?lang=ru")
    b.select_form(nr=0)
    b["source"] = "hello python"
    html = b.submit().get_data()
    soup = BeautifulSoup(html)
    print  soup.find("span", id = "r_text").string
    
  4. Julik

    А отличный рассказ про разные UTF есть тут

  5. siniy

    Pak поднял мучающий меня вопрос:) Как в Python перевести HTML и (особенно мучает:)) Javascript-коды в UTF8-строки?

  6. Иван Сагалаев

    Честно говоря, никогда не приходилось отдельно коды переводить. Если это HTML или XML, то он парсится парсерами (модуль htmllib и семейство модулей xml.* соответственно). А чтобы парсить javascript'овые строки есть, например, simplejson (с недавних пор включается в django.utils.simplejson, кстати). Делается примерно так:

    from simplejson.decoder import JSONDecoder
    JSONDecoder().decode(r'"\\u0410"')
    

    Возвратит питоновскую юникодную строку.

  7. Павел

    Почему бы на ru.wikipedia.org Вам не добавить этот текст?

  8. Иван Сагалаев

    Мне показалось, что там тема проработана глубже, поэтому моя статья и не добавит ничего.

  9. jumper

    Интересная статья. Как раз столкнулся с проблемой - неизвестная четырехбайтовая кодировка. Первые 2 байта C3 90 или C3 91 (шестнадцатиричные), потом еще 2 байта (очевидно конкретно уточняющие символ). В общем выглядит примерно так - C3 90 C2 BA (один символ).

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

Добавить комментарий