I � Unicode
Давайте я вам про Unicode ещё раз расскажу?
Как известно, в памяти компьютера числа представлены битами, которые группируются в байты. Один байт может хранить одно из 256 значений (поскольку состоит из восьми бит, каждый из которых может хранить одно из двух). Следовательно, числа, значения которых > 255 хранятся в больше, чем одном байте.
Например, числа до 65535 можно уместить уже в двух байтах: в так называемом старшем записывается сколько раз полных 256 содержится в числе, а в младшем — остаток: старший × 256 + младший.
В зависимости от типа процессора, порядок, в котором записаны в памяти старшие и младшие байты, различается. Собственно, мне хорошо известны только две системы: младший записывается первым (как в процессорах Intel) и старший записывается первым (в процессорах ARM, которые стоят в смартфонах). Есть ещё смешанная, но с ней я не сталкивался. Системы эти носят имена: little-endian и big-endian (системы со смешанным порядком называются middle-endian и термин не указывает на то как именно «мешается» этот порядок). Краткая запись названий — LE и BE.
Есть ещё системы, которые умеют переключать порядок (те же ARM) и называются bi-endian.
Термины little-endian и big-endian пришли к нам из «Приключений Гулливера» и на русский переводятся как «тупоконечный» и «остроконечный». Те, кто читали, те помнят (война по поводу того с какой стороны разбивать яйца). Информатика тут какбэ намекает. Хотя у каждой системы есть свои достоинства и (не удержался) мне ближе LE.
Сюрприз для непрограммистов: буквы в памяти компьютера тоже представлены числом. Это просто номер по порядку в компьютерном алфавите. Так девочки в нашем классе «кодировали» записки: вместо букв ставили номер позиции в алфавите. В чём-то они были правы, но только не в том, что это шифр.
Когда-то компьютеры победивших сейчас систем использовали всего 256 символов и всем было хорошо — туда умещались все символы, которые присутствовали в том мире, где эти компьютеры создавались. Экспансия привела к тому, что 256 значений для символов перестало хватать.
Было принято очевидное решение — выделять на символ не один байт, а несколько. Так появился стандарт Unicode, где огромному количеству символов дано своё число и закреплены начертания, стандарт дополняется и новые версии выходят почти каждый год.
Система кодирования Unicode, где выделяются два байта, называется UTF-16 (16 бит на символ), там где четыре байта — UTF-32 (32 бита). Название UCS-4 (четыре байта) является синонимом UTF-32, а UCS-2 (два байта) подмножеством UTF-16. UCS-2 отличается от UTF-16 отсутствием так называемых «суррогатных пар» (которые появились только в Unicode 2.0, вы не хотите знать что это) и является устаревшим стандартом, можете про него забыть.
Так как способов хранения чисел, не умещающихся в памяти у нас несколько, то системы подразделяются на UTF-16BE, UTF-16LE, UTF-32BE и UTF-32LE. Отсюда видно, что UCS-4LE это тоже, что и UTF-32LE. Если порядок байт не указан, то принято считать, что используется big-endian.
Первого апреля 2005-го года были предложены шуточные «стандарты» UTF-9 и UTF-18, отношения к рассматриваемой проблеме они имеют. Для телеграфа и прочего слоновьего гуано, разрабатывались UTF-5 и UTF-6, но о их судьбе мне ничего не известно. Так же есть UTF-7, который в стандарт не вошёл, но реально применяется (в модифицированном виде) внутри почтового протокола IMAP4, про него я рассказывать не буду, мне он стал известен из-за оригинального способа его использования для XSS-атак в IE (в частности, решением этой проблемы я занимался в PEAR PHP классе HTML_Safe). Можно упомянуть ещё UTF-1, но с ней я не сталкивался в работе.
BOM. BOM расшифровывается как «byte order mark» (признак порядка байт) и ставится внутри файлов упомянутых двух- и четырёхбайтных кодировок. Если BOM внутри файла не встретился, принимается порядок big-endian. У BOM есть значание. В UCS-2 это 65279 (для программистов — FEFF), для UCS-4/UTF-32 — это 4278124544 (FEFF0000). Число выбрано так, чтобы старшие и младшие байты у них не совпадали и по их порядку можно было бы определить какой порядок байт используется. К сожалению, BOM не даёт возможности определить использутеся двух- или четырёхбайтная кодировка.
Теперь непрограммистам будет трудно.
Пока всё было достаточно просто, но человечество придумало ещё одну кодировку — UTF-8, с плавающим размером. Хорошие новости заключаются в том, что порядок следования байт тут определён и никаких LE и BE рядом с UTF-8 не ставится. Соотвественно и BOM тут не нужен. Он может использоваться только для того, чтобы указать программе, что это именно UTF-8 и имеет номер 15711167 (EF BB BF). Откуда можно сделать вывод (дорогие писатели редакторов), что использование в UTF-8 BOM от UTF-16 — ошибка.
Трудность в том, что UTF-8, по сути это ещё один способ записи многобайтовых чисел (а каждая буква в стандарте Unicode — многобайтовое число). У системы есть целых два плюса (ирония!): старая однобайтовая кодировка совместима с UTF-8, а значит буржуинам не нужно переделывать свои программы, если они не используют в них буквы и других языков (например, на любом старом англоязычном сайте как бы уже используется кодировка UTF-8), второй плюс — латиница записывается компактнее (в один символ). Минусы — чисто программисткие: работа с кодировкой требует больше ресурсов из-за плавающего размера.
Итак. Каждый символ в кодировке занимает от 1 до 4-х байт. Вообще, формат устроен так, что можно было бы взять и более длинные цепочки, но в Unicode нет столько символов, чтобы записывать их более длинными последовательностями.
Тут надо вспомнить что такое биты. Бит — единица информации, мельче не бывает, у него всего два значения — 0 или 1. Байт состоит из 8 битов, биты очень удобно записывать в позиционной двоичной системе: 00001011. «Позиционная» тут означает, что значение числа зависит от его позиции. Кстати, это привычная нам система. В числе «22» две двойки, но у первой значение в десять раз больше, чем у второй. Это десятичная позиционная система. В двоичной, каждая более левая однёрка будет больше в два раза своей соседки.
Таким образом число 1011 расшифровывается из двоичной как 1 × 23 + 0 × 22 + 1 × 21 + 1 × 20 = 1 × 8 + 1 × 2 + 1 × 1 = 8 + 2 + 1 = 11 в десятичной системе.
UTF-8 устроен следующим образом. Пусть, мы двигаемся по строке, содержащей два байта: 208 и 159. В битах это 11010000 и 10011111. (Немного осталось, потерпите).
В первом символе нужно посчитать количество бит со значением «1» до первого нуля. Это общее количество байт, которым записан данный символ. Если количество байт — один (это вроде как специальный признак), то вы нашли не первый байт символа.
У нас в примере количество бит до первого нуля — два. Значит, буква записана двумя символами — первый это тот, на которым мы находимся и второй — который следует за ним. Каждый байт в UTF-8 разбит на две части — до первого нулевого бита. Первая часть — общая длина байт последовательности, а оставшаяся — значение. Биты из значения записывают последовательно (у нас это 10000 011111) и смотрят какое число получилось (у нас это — 1055, это номер буквы «П» в Unicode).
Могу рассказать про UTF-7 и UTF-1, если интересно. Или про суррогатные пары.
Да, интересно. Именно это и интересно.
Осторожно! Интеллектуальный юмор в заголовке!
Комментарий для creagenics.com:
Для тех кто понимает, ага ;)
Первый — это слева или справа?
спасибо!
Вы ещё спросите, «куда растут адреса памяти?». ^_^
«вы не хотите знать что это» — это пародия на «you don’t want to know»?
После слов «Немного осталось, потерпите» текст резко теряет понятность и доступность, хотя там самый цимес остаётся.
...а про суррогатные пары — мы хотим знать, что это, да :)
Есть мнение, что это довольно умозрительный минус. Просто потому, что операции типа «взять произвольный n-ный символ строки» на самом деле редки. Чаще строка просто сканируется целиком в любом случае (копирование, вывод, поиск). А длина строки в развитых системах давно уже таскается вместе со строкой отдельным числом, поэтому для подсчета длины в символах тоже не надо ничего декодировать и искать.
Спасибо, занимательно.
Не совсем понятно, правда, зачем в таком тексте пояснение что такое двоичная система. Думаю, что домохозяйка не сможет прочитать предыдущие два экрана текста, чтобы наконец разобраться, что означает 1011 в двоичной системе.
Комментарий для m-ivanov.livejournal.com:
Ну, я на семитских языках не пишу и считаю слева направо )
Комментарий для gaius-julius.livejournal.com:
Не знаю. Такой английской фразы я не встречал.
Комментарий для astur.net.ru:
Там сложновато, да. Я пытался сделать на примере, чтобы было понятно. Жаль, что не удалось.
Комментарий для softwaremaniacs.org/about/:
Ну, в C это не так (про длину), правда ведь? Да и операции самые разные бывают. При работе с текстом каких только операций не встречается.
Но в скриптовых языках особой проблемы нет, да. И, если я ничего не путаю, внутреннее представление Unicode-cтрок у «пайтона» вовсе не UTF-8.
Комментарий для uemoe.livejournal.com:
Далеко не все сталкиваются с двоичной системой. Многие мои знакомые программисты не знают (и им прекрасно живётся без этого знания) что такое биты.
UTF-8 не является единственной кодировкой с плавающим размером символа. В UTF-16 он тоже плавает. Другое дело, что символы с кодами больше 0x10000 обычно нафиг никому не нужны, а для символов с меньшими кодами UTF-16 совпадает с UCS-2.
Комментарий для david-m.livejournal.com:
Подробнее, пожалуйста, про плавающий размер в UTF-16. Потому что на сайте Unicode считают иначе ( http://www.unicode.org/faq/basic_q.html#14 ):
Комментарий для Евгения Степанищева:
Что вы имеете ввиду фразой «В C это не так (про длину)»? Никто не мешает нам завести структуру { int length; char* str; }. И будет строчка с длиной. C же язык-конструктор в котором почти всегда нужно сначала инструменты из кубиков собрать, а потом уже дом строить.
Комментарий для dmitriy.dzema.name:
Я не спорю, что так можно делать, но я не вижу, что повсеместно так делают. Та же libmapi, которую я ковыряю уже несколько месяцев, преспокойно хранит строки в char*, хотя там много мест, где используется именно UTF-8.
Комментарий для Евгения Степанищева:
http://www.faqs.org/rfcs/rfc2781.html, “2.1 Encoding UTF-16”
Комментарий для david-m.livejournal.com:
Спасибо, нужный кусок:
Исправлю.
Комментарий для Евгения Степанищева:
Ну вот, например:
11010000 и 10011111 преобразуется в 1000011111. А 11010000 и 10000001 во что преобразуется? В 100001 (33), или...? А 11101000, 10000001 и 10000011, соответственно в 1000111 (71)?
Понимаю, что номера символов в примерах не требующие юникодирования. Это просто для ясности.
Комментарий для astur.net.ru:
Я же написал:
Т. е. из двух этих числе получится 1000 и 000001 — 1000000001. Это вот этот символ: http://www.fileformat.info/info/unicode/char/0201/index.htm
Комментарий для Евгения Степанищева:
Тогда получается: 110|10000 и 10|011111 => 10000 и 011111 => 10000011111.
а у тебя: 110|10000 и 100|11111 => 10000 и 11111 => 1000011111.
...или я опять неправильно понял?...
Комментарий для astur.net.ru:
Ой, да. Я тут накосячил, спасибо, что заметил. Поправлено.
Не понял. Может быть, имелось в виду: «один символ против ДВУХ»?
Комментарий для www.alik.su:
Тьфу, спасибо :) Вот что значит торопился :) Поправил.
Реально копатся в представлении нафиг никому не нужно.
самостоянельно парсят строки посимвольно только последние демоны. Пусть этим библиотечный функции занимаются. А им паралельно что ASII что UTF-8. Лишь бы на входе были строки одинаковой кодировки.
Комментарий для teolog.myopenid.com:
Откуда такое красноглазие?
Задачи бывают очень разные. Я, например, сейчас работаю с MAPI, через библиотеку libmapi, парсить за меня различные структуры библиотеки не будут, они просто не умеют.
Да и библиотеки не на деревьях растут, их кто-то пишет.
я не знаю, как где, а в смартфонах одной финской говнофирмы — армы в le режиме.
Комментарий для zg.livejournal.com:
Щито?
little-endian. другими словами, смартфоны — плохой пример big-endian платформы.
Комментарий для zg.livejournal.com:
А. Ну почему же плохой? Обычный.
не «до первого нулевого бита», а «по первому нулевому биту».
Спс, статья гуд. Сам такую у себя фигачил какое-то время назад ))
Комментарий для Евгения Степанищева:
glib вполне себе повсеместный.
Эта загадочная фраза:
«это именно UTF-8 и имеет номер 15711167 (EF BB BF). Откуда можно сделать вывод (дорогие писатели редакторов), что использование в UTF-8 BOM от UTF-16 — ошибка».
На самом деле — эти три байта — просто BOM (U+FEFF), записанный в кодировке
UTF-8. А число 15711167 ввобще не при чем :-).
Комментарий для zelserg.livejournal.com:
Число 15711167 тут так же причём, как и EFBBBF. Это просто две записи одного числа.
У меня вордовский документ большой, я делаю копу-пасте, сохранить так, сохранить этак, чтоб потом прочитать в С++. Мне нужно сделать кой-какие преобразования, причем это большой документ на 2-х языках. В заголовок этой страницы написано ??Unicode. В моем браузере ?? палка и квадратик, у вас скорее всего по другому. Наверно эти символы называются жопа. Я ищу в инете чтото типа <string.h>+unicode. И мне становится грустно
привет. интересная статей какаа про ютф=7 у тебя можно ли узнать по более про неё?
Комментарий для невидимка:
Очень несложный формат: http://en.wikipedia.org/wiki/UTF-7