Иван Сагалаев
Про Юникод написано много, но мне кажется, что это одна из тем, про которую "чисто ради интереса" никто читать не полезет. Поэтому я решил поделиться здесь одной штукой, которая, как я в разговорах со многими замечал, не всем известна. Речь о том, "сколько байт занимает юникод".
Старые кодировки (например 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 (очень хорошая).
Про Юникод написано много, но мне кажется, что это одна из тем, про которую "чисто ради интереса" никто читать не полезет. Поэтому я решил поделиться здесь одной штукой, которая, как я в разговорах со многими замечал, не всем известна. Речь о том, "сколько байт занимает юникод".
Старые кодировки (например 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 (очень хорошая).