Номер телефона

Последнее обновление:

О кодировках и юникоде (unicode)

Абсолютный минимум, который каждый разработчик ПО обязательно, безусловно должен знать о Unicode и наборах символов.

Вы когда-нибудь задумывались об этом загадочном теге Content-Type? Знаете, тот, который вы должны вставить в HTML, и вы никогда не знаете, каким он должен быть? Вы когда-нибудь получали электронное письмо от своих друзей из Болгарии с темой «???? ?????? ??? ????»? Я был встревожен, обнаружив, как много разработчиков ПО на самом деле не полностью в курсе таинственного мира наборов символов, кодировок, Unicode и всего такого.

Пару лет назад бета-тестер FogBUGZ задавался вопросом, может ли он обрабатывать входящую почту на японском языке. Японский? У них есть электронная почта на японском языке? Я понятия не имел. Когда я внимательно изучил коммерческий элемент управления ActiveX, который мы использовали для разбора сообщений электронной почты MIME, мы обнаружили, что он делал совершенно неправильно с наборами символов, поэтому нам пришлось написать героический код, чтобы отменить неправильное преобразование, которое он сделал, и переделать его правильно.

Когда я изучил другую коммерческую библиотеку, у нее тоже была полностью сломанная реализация кода символов. Я переписывался с разработчиком этого пакета, и он как-то подумал, что они «ничего не могут с этим поделать». Как и многие программисты, он просто хотел, чтобы все это как-то само собой прошло. Но этого не произойдет. Когда я обнаружил, что популярный инструмент веб-разработки PHP почти полностью игнорирует проблемы кодировки символов, беспечно используя 8 бит для символов, что делает практически невозможным разработку хороших международных веб-приложений, я подумал, что хватит.

В этой статье я расскажу вам, что именно должен знать каждый работающий программист. Все эти вещи о «простом тексте = ascii = символы 8 бит» не просто неверны, они безнадежно неверны, и если вы все еще программируете таким образом, вы не намного лучше врача, который не верит в микробы. Пожалуйста, не пишите ни одной строчки кода, пока не дочитаете эту статью. Прежде чем начать, я должен предупредить вас, что если вы один из тех редких людей, которые знают об интернационализации, вы найдете все мое обсуждение немного упрощенным. Я просто пытаюсь установить здесь минимальную планку, чтобы каждый мог понять, что происходит, и мог писать код, который может работать с текстом на любом языке, кроме подмножества английского, в котором нет слов с ударениями. И я должен предупредить вас, что обработка символов — это лишь малая часть того, что требуется для создания программного обеспечения, работающего на международном уровне, но я могу писать только об одной вещи за раз, поэтому сегодня это наборы символов.

Историческая перспектива

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

Таблица ASCII
 Таблица ASCII

В полустарые времена, когда изобреталась Unix, а K&R писали язык программирования C, все было очень просто. EBCDIC был на грани исчезновения. Единственными символами, которые имели значение, были старые добрые английские буквы без ударения, и у нас был код для них под названием ASCII, который мог представить каждый символ, используя число от 32 до 127. Пробел был 32, буква «A» была 65 и т. д. Это можно было удобно хранить в 7 битах.

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

Коды ниже 32 назывались непечатаемыми и использовались для управляющих символов, например, 7, который заставлял ваш компьютер издавать звуковой сигнал, и 12, который заставлял текущую страницу бумаги вылетать из принтера и загружать новую. И все было хорошо, если вы были носителем английского языка. Поскольку байты имеют место для восьми бит, многие люди подумали: «Боже мой, мы можем использовать коды 128-255 для наших собственных целей».

Проблема была в том, что у многих людей была эта идея в одно и то же время, и у них были свои собственные идеи о том, что и где должно располагаться в пространстве от 128 до 255. У IBM-PC было что-то, что стало известно как набор символов OEM, который предоставлял некоторые символы с акцентами для европейских языков и кучу символов рисования линий... горизонтальные полосы, вертикальные полосы, горизонтальные полосы с маленькими звеньями, свисающими с правой стороны, и т. д., и вы могли использовать эти символы рисования линий, чтобы сделать шикарные коробки и линии на экране, которые вы все еще можете видеть работающими на компьютере 8088.

Типичная кодовая страница символов в одной из кодировок
 

Фактически, как только люди начали покупать ПК за пределами Америки, были придуманы всевозможные наборы символов OEM, которые все использовали верхние 128 символов для своих собственных целей. Например, на некоторых ПК код символа 130 отображался как é, но на компьютерах, продаваемых в Израиле, это была еврейская буква Гимел (ג), поэтому, когда американцы отправляли свои резюме в Израиль, они приходили как rגsumגs. Во многих случаях, например, в русском языке, существовало много разных идей о том, что делать с верхними 128 символами, поэтому вы даже не могли надежно обмениваться русскими документами.

В конце концов, этот OEM-бесплатный для всех был кодифицирован в стандарте ANSI. В стандарте ANSI все соглашались с тем, что делать ниже 128, что было почти то же самое, что и ASCII, но было много разных способов обработки символов от 128 и выше, в зависимости от того, где вы жили. Эти разные системы назывались кодовыми страницами.

Так, например, в Израиле DOS использовала кодовую страницу под названием 862, в то время как греческие пользователи использовали 737. Они были одинаковыми ниже 128, но отличались от 128 и выше, где находились все забавные буквы. Национальные версии MS-DOS имели десятки таких кодовых страниц, обрабатывающих все от английского до исландского, и у них даже было несколько «многоязычных» кодовых страниц, которые могли работать с эсперанто и галисийским на одном компьютере! Ого! Но получить, скажем, иврит и греческий на одном компьютере было совершенно невозможно, если вы не написали свою собственную программу, которая отображала все с помощью растровой графики, потому что иврит и греческий требовали разных кодовых страниц с разной интерпретацией высоких чисел.

Тем временем в Азии творились еще более безумные вещи, чтобы учесть тот факт, что азиатские алфавиты состоят из тысяч букв, которые никогда не уместятся в 8 бит. Обычно это решалось с помощью запутанной системы под названием DBCS, «двухбайтового набора символов», в которой некоторые буквы хранились в одном байте, а другие занимали два. Было легко двигаться вперед по строке, но почти невозможно двигаться назад.

Программистов призывали не использовать s++ и s– для перемещения вперед и назад, а вместо этого вызывать такие функции, как AnsiNext и AnsiPrev в Windows, которые знали, как справиться со всем этим беспорядком. Но все равно большинство людей просто притворялись, что байт — это символ, а символ — это 8 бит, и пока вы не переносили строку с одного компьютера на другой или не говорили больше чем на одном языке, это всегда работало. Но, конечно, как только появился Интернет, перемещение строк с одного компьютера на другой стало довольно обычным делом, и весь беспорядок рухнул. К счастью, был изобретен Unicode.

Unicode

Unicode был смелой попыткой создать единый набор символов, который включал бы все разумные системы письма на планете, а также некоторые вымышленные, такие как клингонский. Некоторые люди ошибочно полагают, что Unicode — это просто 16-битный код, где каждый символ занимает 16 бит, и, следовательно, существует 65 536 возможных символов. На самом деле это не так. Это самый распространенный миф о Unicode, так что если вы так думали, не расстраивайтесь. На самом деле, в Unicode другой способ мышления о символах, и вам нужно понять способ мышления Unicode, иначе ничто не будет иметь смысла.

До сих пор мы предполагали, что буква сопоставляется с некоторыми битами, которые можно хранить на диске или в памяти:

A -> 0100 0001

В Unicode буква сопоставляется с чем-то, называемым кодовой точкой, которая все еще является лишь теоретической концепцией. То, как эта кодовая точка представлена в памяти или на диске, — это целая история. В Unicode буква A — это платонический идеал. Она просто парит на небесах: A Это платоническое A отличается от B и отличается от a, но то же самое, что А или А.

Идея о том, что A в шрифте Times New Roman — это тот же символ, что и в шрифте Helvetica, но отличается от «a» в нижнем регистре, не кажется очень спорной, но в некоторых языках простое выяснение того, что это за буква, может вызвать споры. Является ли немецкая буква ß настоящей буквой или просто причудливым способом написания ss? Если форма буквы меняется в конце слова, это другая буква? Иврит говорит «да», арабский говорит «нет». В любом случае, умные люди в консорциуме Unicode выясняли это в течение последнего десятилетия или около того, что сопровождалось большим количеством весьма политических дебатов, и вам не нужно об этом беспокоиться. Они уже все выяснили.

Каждой платонической букве в каждом алфавите консорциум Unicode присваивает магическое число, которое записывается так: U+0639. Это магическое число называется кодовой точкой. U+ означает «Unicode», а числа являются шестнадцатеричными. U+0639 — это арабская буква Ain. Английская буква A будет иметь вид U+0041. Вы можете найти их все с помощью утилиты Charmap в Windows 2000/XP или посетив веб-сайт Unicode. Нет реального ограничения на количество букв, которые может определить Unicode, и на самом деле они превысили по количеству 65 536, так что не все буквы Unicode можно втиснуть в два байта.

Хорошо, допустим, у нас есть строка:

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; это называется меткой порядка байтов Unicode (BOM), и если вы меняете местами свои высокие и низкие байты, это будет выглядеть как FF FE, и человек, читающий вашу строку, будет знать, что ему нужно менять местами каждый второй байт.

Не каждая строка Unicode в дикой природе имеет метку порядка байтов в начале. Какое-то время казалось, что этого может быть достаточно, но программисты жаловались. «Посмотрите на все эти нули!» — сказали они, поскольку они были американцами и смотрели на английский текст, в котором редко использовались кодовые точки выше U+00FF. А еще они были либеральными хиппи в Калифорнии, которые хотели сэкономить (ухмылка).

Если бы они были техасцами, они бы не возражали против того, чтобы пожирать вдвое больше байтов. Но эти калифорнийские слабаки не могли вынести идею удвоения объема памяти, необходимого для строк, и, в любом случае, уже были все эти чертовы документы, использующие различные наборы символов ANSI и DBCS, и кто собирается преобразовывать их все?

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

UTF-8 была еще одной системой для хранения вашей строки кодовых точек Unicode, этих волшебных чисел U+, в памяти с использованием 8-битных байтов. В UTF-8 каждая кодовая точка от 0 до 127 хранится в одном байте. Только кодовые точки 128 и выше хранятся с использованием 2, 3, на самом деле, до 6 байт.

Это имеет приятный побочный эффект, что английский текст выглядит точно так же в UTF-8, как и в ASCII, поэтому американцы даже не замечают ничего неправильного. Только остальной мир (все остальные языки, за исключением англоязычных) должен прыгать через обручи. В частности, Hello, которое было

U+0048 U+0065 U+006C U+006C U+006F,

будет сохранено как

48 65 6C 6C 6F,

что, смотрите(!), то же самое, что и в ASCII, и ANSI, и в каждом наборе символов OEM на планете. Теперь, если вы настолько смелы, чтобы использовать буквы с ударением или греческие буквы или буквы клингонского алфавита, вам придется использовать несколько байтов для хранения одной кодовой точки, но американцы никогда этого не заметят.

UTF-8 также обладает приятным свойством, что старый невежественный код обработки строк, который хочет использовать один нулевой байт в качестве нулевого терминатора, не будет обрезать строки.

Пока что шла речь о трех способах кодирования Unicode. Традиционные методы сохранения в двух байтах называются UCS-2 (потому что у него два байта) или UTF-16 (потому что у него 16 бит), и все еще нужно выяснить, является ли это UCS-2 с высоким порядком байтов или UCS-2 с низким порядком байтов. И есть популярный новый стандарт UTF-8, который обладает приятным свойством также работать достойно, если у вас есть счастливое совпадение английского текста и программ, которые совершенно не знают, что есть что-то, кроме ASCII.

На самом деле существует множество других способов кодирования Unicode. Есть что-то под названием UTF-7, что очень похоже на UTF-8, но гарантирует, что старший бит всегда будет равен нулю, так что если вам придется пропустить Unicode через какую-то драконовскую полицейскую государственную систему электронной почты, которая думает, что 7 бит вполне достаточно, они все равно смогут пропустить это без потерь.

Есть UCS-4, который хранит каждую кодовую точку в 4 байтах, у которого есть приятное свойство, что каждая отдельная кодовая точка может храниться в том же количестве байтов, но, черт возьми, даже техасцы не будут настолько смелыми, чтобы тратить так много памяти.

И на самом деле теперь, когда вы думаете о вещах в терминах идеальных букв, которые представлены кодовыми точками Unicode, эти кодовые точки Unicode также могут быть закодированы в любой старой схеме кодирования! Например, вы можете закодировать строку Unicode для Hello

U+0048 U+0065 U+006C U+006C U+006F

в ASCII или в старой греческой кодировке OEM, или в еврейской кодировке ANSI, или в любой из нескольких сотен кодировок, которые были изобретены до сих пор, с одной загвоздкой: некоторые буквы могут не отображаться! Если нет эквивалента для кодовой точки Unicode, которую вы пытаетесь представить, в кодировке, которую вы пытаетесь представить введя его, вы обычно получаете маленький вопросительный знак: ? или квадратик. Что вы получили?

-> �

Существуют сотни традиционных кодировок, которые могут правильно хранить только некоторые кодовые точки и заменять все остальные кодовые точки вопросительными знаками. Некоторые популярные кодировки английского текста — Windows-1252 (стандарт Windows 9x для западноевропейских языков) и ISO-8859-1, также известный как Latin-1 (полезна для любого западноевропейского языка).

Но попробуйте сохранить русские или еврейские буквы в этих кодировках, и вы получите кучу вопросительных знаков. UTF- 7, 8, 16 и 32 обладают приятным свойством правильно хранить любую кодовую точку.

Самый важный факт о кодировках

Если вы полностью забыли все, что я только что объяснил, пожалуйста, запомните один чрезвычайно важный факт. Не имеет смысла иметь строку, не зная, какую кодировку она использует. Вы больше не можете притворяться, что «простой» текст — это ASCII. Нет такой вещи, как простой текст. Если у вас есть строка в памяти, в файле или в сообщении электронной почты, вы должны знать, в какой кодировке она находится, иначе вы не сможете ее интерпретировать или правильно отобразить пользователям.

Почти каждая глупая проблема «мой веб-сайт выглядит как тарабарщина» или «она не может читать мои письма, когда я использую ударения» сводится к одному наивному программисту, который не понимает простого факта: если вы не скажете мне, закодирована ли конкретная строка с помощью UTF-8 или ASCII или ISO 8859-1 (Latin 1) или Windows 1252 (WebStation), вы просто не сможете правильно отобразить ее или даже выяснить, где она заканчивается.

Существует более сотни кодировок и выше кодовой точки 127.

Как мы сохраняем эту информацию о том, какую кодировку использует строка?

Что ж, есть стандартные способы сделать это. Для сообщения электронной почты от вас ожидается строка в заголовке формы

Content-Type: text/plain; charset="UTF-8"

Для веб-страницы первоначальная идея заключалась в том, что веб-сервер будет возвращать аналогичный заголовок http Content-Type вместе с самой веб-страницей — не в самом HTML, а как один из заголовков ответа, отправляемых перед HTML-страницей. Это вызывает проблемы.

Предположим, у вас есть большой веб-сервер с множеством сайтов и сотнями страниц, созданных множеством людей на множестве разных языков, и все они используют любую кодировку, которую их копия редактора Microsoft FrontPage посчитала подходящей для генерации. Сам веб-сервер на самом деле не будет знать, в какой кодировке был написан каждый файл, поэтому он не сможет отправить заголовок Content-Type.

Было бы удобно, если бы вы могли поместить Content-Type файла HTML прямо в сам файл HTML, используя какой-то специальный тег. Однако... как вы можете прочитать файл HTML, не зная, в какой он кодировке?!

К счастью, почти каждая общепринятая кодировка делает то же самое с символами от 32 до 127, поэтому вы всегда можете зайти так далеко на HTML-странице, не начав использовать странные буквы:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

Но этот метатег действительно должен быть самым первым в разделе <head>, потому что как только веб-браузер увидит этот тег, он прекратит анализ страницы и начнет заново после переосмысления всей страницы с использованием указанной вами кодировки.

Что делают веб-браузеры, если они не находят Content-Type ни в заголовках http, ни в метатеге?

Internet Explorer на самом деле делает кое-что довольно интересное: он пытается угадать, основываясь на частоте, с которой различные байты появляются в типичном тексте в типичных кодировках различных языков, какой язык и кодировка использовались. Поскольку различные старые 8-битные кодовые страницы имели тенденцию помещать свои национальные буквы в разные диапазоны между 128 и 255, и поскольку каждый человеческий язык имеет свою характерную гистограмму использования букв, это на самом деле имеет шанс сработать.

Это действительно странно, но, похоже, это работает достаточно часто, чтобы наивные авторы веб-страниц, которые никогда не знали, что им нужен заголовок Content-Type, смотрели на свою страницу в веб-браузере, и она выглядела нормально, пока в один прекрасный день они не написали что-то, что не совсем соответствует распределению частот букв их родного языка, и Internet Explorer решил, что это корейский язык, и отобразил его таким образом, доказывая, я думаю, что закон Постеля о том, что «быть консервативным в том, что вы испускаете, и либеральным в том, что вы принимаете», откровенно говоря, не является хорошим инженерным принципом.

В любом случае, что делает бедный читатель веб-сайта, написанного на болгарском, но, похоже, на корейском (и даже не на связном корейском)? Он использует View | Меню кодировки и пробует кучу разных кодировок (есть по крайней мере дюжина для восточноевропейских языков), пока картина не станет яснее. Если бы он знал, как это сделать, чего большинство людей не знают.

Источник: https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/


Комментарии:
Всего комментариев:0
Пожалуйста, не забудьте ознакомиться с правилами оставления комментариев.



Подписаться на комментарии на этой странице

Мы можем выполнить

Другие услуги
Интересная и полезная
информация
НАПИШИТЕ НАМ
Яндекс.Метрика
Номер телефона
© Copyright Все права защищены 2013-2024 Научный консалтинг