By Jouel Spolski
Абсолютно для каждого программиста абсолютный минимум знаний о наборах символов и Unicode, без которых решительно не обойтись. (Без возражений!)
Джоэл Спольски, Среда, 8 октября 2003
Вы никогда не задумывались о таинственном теге Content-Type? Того самого, который неизвестно как выглядит, если его надо вставить в HTML документ? Вы когда-нибудь получали электронную почту от своих друзей из Болгарии с темой «???? ???????? ????»?
Осознав, что множество разработчиков программного обеспечения абсолютно не компетентны во всем, что касается наборов символов, кодировкой, Unicode, считая все это таинственным миром, я был разочарован. Пару лет назад, при бета-тестировании
Сегодня я официально заявляю, что если Вы программист и в 2003 году не знаете основ кодировки символов, Unicode и я Вас на этом поймаю — накажу, заставлю полгода чистить лук на подводной лодке. Клянусь — я это сделаю.
Кроме того,
ИТ не так уж сложны
В этой заметке я попытаюсь объяснить то, что должен знать каждый реальный программер. Выражение «Текст = ASCII символ = 8 бит» — не просто ошибка, а воинствующее и вопиющее незнание. Если, занимаясь программированием, Вы все еще так думаете, то Вы не намного лучше врача, который верит в бессмертие. Остановитесь, пожалуйста, не торопитесь писать еще одну строчку кода до тех пор, пока не прочитаете эту заметку.
Прежде чем я начну, должен сразу предупредить — если вы один из тех редких людей, которые знают об интернационализации, то, скорее всего, найдёте все мои рассуждения немного упрощёнными. Действительно, здесь я просто пытаюсь показать тот минимальный порог, который каждый должен перешагнуть для понимания происходящего и, возможно, написать код, только сулящий надежду на обработку текста с любым языком, кроме английского. И ещё должен предупредить, что принципы обработки — это только малая часть того, что необходимо для создания программного обеспечения международного уровня. Но одновременно писать я могу только об одном, а сегодня это наборы символов.
Историческая справка
Понять это проще всего, если заглянуть в историю. Не думаете, что сейчас я расскажу об очень старых наборах символов, например, EBCDICએ. Не буду, нет. EBCDICએ не имеет никакого отношения к нашей жизни. Так далеко углубляться в прошлое не надо. Вернёмся в лишь в недалёкое прошлое. Когда был придуман Unixએ и K&R писали о
Большинство компьютеров тех дней использовали 8-битные байты, что позволяло особо прижимистым, скаредно экономить, использовав резерв в собственных целях — тусклая луковица WordStarએ фактически превратила свободный бит в индикатор последней буквы слова и это обрекло WordStar к обработке только английских текстов.
Все, что меньше 32 считалось нецензурными, предназначенным исключительно для ругательств. Шутка такая. На самом деле — это символы управления, например, 7 — это звуковой сигнал, которые должен изрекать компьютер, а 12 выбрасывает текущую страницу бумаги из принтера и на прокорм забирает новую.
И все бы было ничего, если бы был только английский. Постольку поскольку в байт помещалось восемь бит, то многим пришло в голову — «черт возьми, для себя любимого можно использовать коды диапазона 128-255». Но всё дело в том, что эта мысль пришла одновременно ко многим, а у каждого свои собственные идеи по использованию пространства от 128 до 255. У IBM-PC это выродилось в наборы OEMએ символов, где представлены нетрадиционные для английского символы европейских языков, а так же
В итоге по либеральному принципу «свободно для всех» OEM стало стандартом ANSIએ кодировок. Согласно стандарту ANSIએ коды до 128 полностью соответствуют ASCIIએ, а символы с кодами от 128 и выше зависят от Вашего местожительства с полной свободой реализации. Такие различия операционных систем назвали
А тем временем в Азии творится вообще полное безумие с учетом того факта, что азиатские алфавиты содержат тысячи символов, т.е. все их, ну никак, не вписать в 8 бит. Как правило, использовались «грязные» методы типа DBCSએ, «Double Byte Character Set», где часть букв кодируются одним байтом, а другая — двумя. В такой строке легко перемещаться вперед, но, чёрт возьми, совершенно не возможно назад. Для движения по строке назад и вперед программистам рекомендовалось не использовать *S++ и *S—, а вместо этого применять функции типа AnsiNext и AnsiPrev в Windows, которые научили бороться со всем этим кошмаром.
И все же до сих пор большинство просто делает вид, что байт — это все ещё 8 бит потому, что это всегда работает, если не переносить строки с компьютера на компьютер и говорить только на одном английском. Конечно, с появлением Интернета, когда передача текстов между компьютерами стала просто вынужденной необходимостью, весь этот порядок рухнул. Но к счастью изобрели Unicode.
Unicode
Unicodeએ — это смелая попытка создания единого набора символов, включающего все разумные системы письма на планете и даже некоторые, искусственно созданные такие, например, как «клингонский языкએ». Многие заблуждаются, считая Unicode просто 16-разрядным кодом, где каждому символу отводится 16 бит, т.е. 65 536 возможных комбинаций. На самом деле все не так. Это самый распространенный миф о Unicode, а если вы так думаете, то пусть же Вам будет хуже.
Фактически, в Unicode несколько иное представление о персонажах и Вы должны понимать мотивы, иначе все остальное теряет смысл. До сих пор мы предполагали, что образ символ соответствует некоему набору бит, который хранится на диске или в памяти:
А -> 0100 0001
В Unicodeએ символом называют код местоположения в таблице, а не битовый образ, — это теоретическая концепция. Именно, код места хранится в памяти или на диске. Образ А в Unicode — это платонический идеал. Это просто, парящий в небе символ A.
Этот платонический А отличается от B, точно так же, как а отличается А, но А не отличается от A, хотя отличается от а. Идея того, что «А» шрифта Times New Roman то же самое, что и «А» шрифта Helvetica, но отличается от «а» в нижнем регистре, не кажется очень уж спорной. Однако, выясняется, что в некоторых языках графический образ может быть сомнительным. Немецкий символ β – это действительно символ или причудливый способ написания ss? Если символ изменен в конце слова, является ли он другой буквой? Евреи говорят «да», а арабы «нет». Но по любому, Вам не придется об этом беспокоиться потому, что умные люди из консорциума Unicode на протяжении последнего десятилетия или около того только тем и занимались, что выясняли всё это, проводя большие и высокопарные политические споры для всеобщей ясности и понимания.
Всякому платоническому символу всякого алфавита консорциумом Unicode присвоено магическое число, которое записывается в виде U+0639. Это магическое число называется кодом места. U+ означает «Unicode», далее шестнадцатеричное число. U+0639 — это арабское Айн. Английская буква А — U+0041. Вы можете найти их все с помощью утилиты отображении символов вWindows 2000/XP или посетив
В действительности в Unicode нет никаких ограничений на количество мест, даже если его надо больше чем 65 536 и символу надо сопоставить код размером большим чем два байта. Вот так и ни как иначе рушатся мифы.
Итак, у нас есть строка:
Hello,
что в Unicode соответствует пяти кодам:
U+0048 U+0065 U+006C U+006C U+006F.
Просто куча кодов. В действительности — это номера. Мы еще ничего не говорил о том, как эта куча хранится в памяти или представлена в электронной почте.
Кодировка
Вот, где введение в кодировку. Первая мысль о Unicode, которая и стала источником мифа о двух байтах, — Эй, давайте хранить эти цифры просто в двух байтах. Так Hello превращается в
00 48 00 65 00 6C 00 6C 00 6F
Верно? Но, не спешите! А не то же ли самое:
48 00 65 00 6C 00 6C 00 6F 00?
Ну, на самом деле, по технике это одно и то же и я уверен, что первые разработчики были в сомнениях — для какого конкретного режима работы процессора естественней хранить номера Unicode в старших байтах, а для какого в младших, но был вечер, и было утро, и вот уже два способа хранения Unicode. Так люди вынуждены придумать странные соглашения о записи FE FF в начале каждой строки Unicode. Это называется
Какое-то время всем всё казалось довольно удобным, но заворчали программисты. «Посмотрите на все эти нули!» — говорили они потому, что были американцами и смотрели только на английский текст, где редко используются номера более U+00FF. Более того, все эти либеральные Калифорнийские хиппи не хотели расти над собой. Техасцы даже не думали бы о двукратном поглощении байтов (прикол). Но эти калифорнийские слабаки не смогли переварить идею удвоения памяти для хранения текстов. Так или иначе, но уже есть масса ANSI и DBCS документов и, черт возьми, кто будет их конвертировать? Мы? Вот только по этой причине на протяжении нескольких лет большинство решительно игнорировало Unicode, а за это время всё стало совсем плохо.
В итоге, появилась блестящая концепция
Английский текст выглядит в UTF-8 точно так же, как в ASCII, в чем нет ничего плохого. Но американцы даже не замечают этого галантного побочного эффекта. А весь остальной мир вынужден подпрыгивать в кольце. В частности, Hello, U+0048 U+0065 U+006C U+006C U+006F, будет храниться в виде 48 65 6C 6C 6F, вот! Точно так же как он хранится в ASCII, ANSI и любом OEM наборе планеты. Теперь, если вы возьмете на себя смелость использовать нестандартные или греческие буквы, клингонский язык, и должны будете использовать несколько байт для хранения одного номера символа, то американцы этого просто не заметят. (Кроме того, UTF-8 обладает хорошим свойством не обрезать строки в косолапой старой строке, где байт со значение NULL используется как нуль-терминатор).
До сих пор я говорил о трёх способах кодирования Unicode. Традиционные store-it-in-two-byte методы называются UCS-2એ (потому что два байта) или UTF-16 (потому что 16 бит) и вы поняли, что такое UCS-2 старшего байта и UCS-2 младшего байта. Новый популярный
На самом деле есть масса других способов кодирования Unicode. Например, так называемый, UTF-7એ, который очень похож на UTF-8એ, но с гарантией нулевого значения старшего бита, что дает ему возможность, при необходимости, пробиться через всякие драконовские ограничения системы электронной полиции. Думается, что 7 битов Unicode вполне достаточно, что бы пролезть там без искажений.
Есть UCS-4એ, где каждый код места хранится в 4 байтах, обладающий замечательным свойством, хранения кода в фиксированном количестве байт, но, ей-богу, в самом деле, даже техасцы не настолько решительны, что бы жертвовать под отходы такую уйму памяти.
Надеюсь теперь, Вы думаете о сущности с точки зрения платонического идеала буквы, представленной номером в Unicode, которое может быть закодировано по схеме любой не слишком древней школы кодирования! Например, вы можете кодировать Unicode строку Hello (U+0048 U+0065 U +006C U+006C U+006F) в ASCII или старом OEM греческой кодировки, или иврите ANSI кодировки, или любой другой из нескольких сотен кодировок, изобретённых до сих пор с пониманием того, что некоторые буквы могут просто не отображаться! Если номер Unicode не заполнен, а Вы пытаетесь показать этот символ, то, обычно, получаете небольшой знак вопроса «?», а если Вы действительно хороши, то вопрос в коробке �. У Вас какой?
Есть сотни традиционных кодировок, где верно хранятся только некоторые номера, а все остальные места Unicode заменяются вопросительным знаком. Наиболее популярными кодировками текста для английского являются Windows-1252એ (Windows 9x стандарт западноевропейских языков) и
Самое важное в кодировках
Если вы полностью забудите все, что я только что сейчас объяснил, помните одно чрезвычайно важное обстоятельство — строка не имеет смысла, если не известна её кодировка. И Вы больше не можете прятать голову в песок и делать вид, что «просто текст» — это ASCII.
Нет такого понятия «просто текст».
Если у вас есть строка, в памяти, в файле или в сообщении электронной почты, то необходимо точно знать в какой она кодировке, иначе Вы не сможете её правильно интерпретировать и показать пользователям.
Почти каждый брюзжал — «мой сайт выглядит бредом» или «она не может прочитать мои письма не на английском». А проблему создал один наивный программист, который не понимает простого факта — если ему не сказали, что конкретная строка закодирована UTF-8, ASCII, ISO 8859-1 (Latin 1) или Windows 1252 (западноевропейская), то просто невозможно правильно её отобразить, более того, даже выяснить, где же она заканчивается.
Существует более ста кодировок, где номера более 127 полностью отсутствуют.
Как сохранить информацию о кодировке строки? Для этого есть несколько стандартных способов. Для сообщений электронной почты, вам потребуется строка в заголовке формы
Content-Type: text/plain; charset=»UTF-8″
Для веб-страниц была оригинальная идея — веб-сервер в заголовке http возвращает аналогичный Content-Type, т.е. не в HTMLએ странице, а в заголовке, который отправляются до передачи самой HTML страницы.
Однако, в этом есть проблема. Допустим, у вас большой веб-сервер с огромным количеством сайтов и миллионами страниц, размещенных множеством людей на самых разных языках с использованием любых возможных кодировок, которые по своему усмотрению генерирует MS FrontPageએ. Веб-сервер сам по себе не может знать кодировку каждого файла и поэтому не может отправить соответствующий Content-Type заголовка.
Было бы удобно получить возможность размещать Content-Type в самом файле HTML, используя какой-то специальный тег. Конечно, пуристы сочтут Вас за сумасшедшего … Как вы можете прочитать HTML файл, если не знаете в какой он кодировке?! К счастью, почти у всех кодировок коды всех символов от 32 до 127 одинаковы, так что вы всегда можете прочитать страницу HTML без использования смешанных записей до:
Этот мета тег действительно должен быть самым первым, прочитав его, веб-браузер останавливает дальнейшую обработку страницы и заново начинает все переосмысливать, применяя полученную от Вас кодировку.
Что делают веб-браузеры, если не находят Content-Type в HTTPએ заголовке или в мета-теге? Internet Explorer на деле предпринимает что-то довольно интересное — по частоте появления характерных байт в характерных местах текста он пытается угадать язык и кодировку. По-столько по-скольку, старых 8 битовых страниц мало, а национальные буквы, из диапазона между 128 и 255, имеют характерные частоты распределения в текстах различных человечьих языков, то это, скорее всего, имеет шансы работать. По настоящему странно то, что это работает довольно часто потому, что веб-страницы наивных писателей, которые никогда не знали о Content-Type в заголовке, выглядят в web-браузере нормально, пока в один прекрасный день, они не начнут писать тексты с отличным от их родного языка частотным распределение букв. Тогда Internet Explorer решит, что это корейский и соответствующим образом отобразит, доказывая, как думается, закон Постела «Будь требователен к тому, что отсылаешь, и либерален к тому, что принимаешь», откровенно неважный принцип для инженера. Во всяком случае, что делает бедный читатель болгарского сайта, который показан на корейском (и даже фрагментарно на корейском)? В главном меню «Просмотр» пункт «Кодировка» он пытается перебирать кучу разных кодировок (по крайней мере дюжину для восточн-европейских языков), пока страница не прояснится. Если он знает, что делать, но большинство об этом не знает.
В программном обеспечении последней версии
Когда CityDesk публикуется, то веб-страницы преобразуются в UTF-8, который на протяжении многих лет замечательно поддерживается большинством веб-браузеров. Вот так кодируются все
Заметка становится довольно нудной, хотя, все еще не охватила всего того, что надо знать о кодировках и Unicode. Однако, надеюсь, что ежели Вы дочитали до сюда, то знаете достаточно и можете вернуться к программированию без пиявок и заклинаний, применив прописанные здесь антибиотики.
The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!), опубликовано К ВВ, лицензия — Creative Commons Attribution-NonCommercial 4.0 International.
1 нравится это