Давайте я вам про 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, если интересно. Или про суррогатные пары.
Комментарии 41
Первый — это слева или справа?
Вы ещё спросите, «куда растут адреса памяти?». ^_^
…а про суррогатные пары — мы хотим знать, что это, да :)
Есть мнение, что это довольно умозрительный минус. Просто потому, что операции типа «взять произвольныйn-ный символ строки» на самом деле редки. Чаще строка просто сканируется целиком в любом случае (копирование, вывод, поиск). А длина строки в развитых системах давно уже таскается вместе со строкой отдельным числом, поэтому для подсчета длины в символах тоже не надо ничего декодировать и искать.
Но в скриптовых языках особой проблемы нет, да. И, если я ничего не путаю, внутреннее представление
Не совсем понятно, правда, зачем в таком тексте пояснение что такое двоичная система. Думаю, что домохозяйка не сможет прочитать предыдущие два экрана текста, чтобы наконец разобраться, что означает 1011 в двоичной системе.
Подробнее, пожалуйста, про плавающий размер вUTF-16. Потому что на сайте Unicode считают иначе ( http://www.unicode.org/faq/basic_q.html#14 ):
Что вы имеете ввиду фразой «В C это не так (про длину)»? Никто не мешает нам завести структуру { int length; char* str; }. И будет строчка с длиной. C же
http://www.faqs.org/rfcs/rfc2781.html, “2.1 Encoding
Комментарий для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/ch … /index.htm
Тогда получается: 110|10000 и 10|011111 => 10000 и 011111 => 10000011111.
а у тебя: 110|10000 и 100|11111 => 10000 и 11111 => 1000011111.
…или я опять неправильно понял?…
Ой, да. Я тут накосячил, спасибо, что заметил. Поправлено.
Не понял. Может быть, имелось в виду: «один символ против ДВУХ»?
самостоянельно парсят строки посимвольно только последние демоны. Пусть этим библиотечный функции занимаются. А им паралельно что ASII что
Задачи бывают очень разные. Я, например, сейчас работаю с MAPI, через библиотеку libmapi, парсить за меня различные структуры библиотеки не будут, они просто не умеют.
Да и библиотеки не на деревьях растут, их
А. Ну почему же плохой? Обычный.
Спс, статья гуд. Сам такую у себя фигачил
Комментарий для Евгения Степанищева:
glib вполне себе повсеместный.
«это именно
На самом деле — эти три байта — просто BOM (U+FEFF), записанный в кодировке
за 16 лет не дошло?