В каком месте файла может использоваться bom маркер последовательности байтов

Обновлено: 29.09.2022

Byte Order Mark (BOM) - Unicode символ, используемый для индикации порядка байтов текстового файла. Его кодовый символ U+FEFF. По спецификации его использование не является обязательным, однако если BOM используется, то он должен быть установлен в начале текстового файла. Помимо своего конкретного использования в качестве указателя порядка байтов, символ может также указать какой кодировкой Unicode закодирован текст.

Кодировка Unicode может использовать 16-битные или 32-разрядных числа и приложение должно знать как дальше с ним поступать. Поэтому потребность в BOM возникает при обмене документами.

Примечания

Wikimedia Foundation . 2010 .

Смотреть что такое "Byte order mark" в других словарях:

Byte-order mark — A byte order mark (BOM) is the Unicode character at code point U+FEFF ( zero width no break space ) when that character is used to denote the endianness of a string of UCS/Unicode characters encoded in UTF 16 or UTF 32. It is conventionally used… … Wikipedia

Byte Order Mark — Als Byte Order Mark (BOM, dt. „Bytereihenfolge Markierung“) wird das Unicode Zeichen U+FEFF (englisch zero width non breaking space) am Anfang eines Datenstroms bezeichnet, wo es als Kennung zur Definition der Byte Reihenfolge und… … Deutsch Wikipedia

Byte Order Mark — Marque d ordre des octets Pour les articles homonymes, voir BOM. Unicode Jeux de caractères UCS (ISO/CEI 10646) ISO 646, ASCII ISO 8859 1 WGL4 UniHan Équivalences normalisées NFC … Wikipédia en Français

byte order mark — baitų eiliškumo ženklas statusas T sritis informatika apibrėžtis ↑Ženklas, nurodantis ↑baitų eiliškumą ↑unikodo tekste. Kodas U+FFFE. Rašomas unikodo teksto failo pradžioje. Šio ženklo baitus procesorius skaito jame nustatyta tvarka. Jeigu jis… … Enciklopedinis kompiuterijos žodynas

Byte-Order — Die Byte Reihenfolge (engl.: Byte Order oder Endianness) bezeichnet die Speicherorganisation für einfache Zahlenwerte, in erster Linie die Ablage von ganzzahligen Werten (Integer) im Arbeitsspeicher. Eine Festlegung des zu verwendenden… … Deutsch Wikipedia

Byte Order — Die Byte Reihenfolge (engl.: Byte Order oder Endianness) bezeichnet die Speicherorganisation für einfache Zahlenwerte, in erster Linie die Ablage von ganzzahligen Werten (Integer) im Arbeitsspeicher. Eine Festlegung des zu verwendenden… … Deutsch Wikipedia

Byte order — Die Byte Reihenfolge (engl.: Byte Order oder Endianness) bezeichnet die Speicherorganisation für einfache Zahlenwerte, in erster Linie die Ablage von ganzzahligen Werten (Integer) im Arbeitsspeicher. Eine Festlegung des zu verwendenden… … Deutsch Wikipedia

Network Byte Order — Die Byte Reihenfolge (engl.: Byte Order oder Endianness) bezeichnet die Speicherorganisation für einfache Zahlenwerte, in erster Linie die Ablage von ganzzahligen Werten (Integer) im Arbeitsspeicher. Eine Festlegung des zu verwendenden… … Deutsch Wikipedia

Left-to-right mark — The left to right mark (abbr|LRM|left to right mark) is a non printing character used in the computerized typesetting of bi directional text containing mixed left to right scripts (such as English and Russian) and right to left scripts (such as… … Wikipedia

Order of the Occult Hand — The Order of the Occult Hand is a whimsical secret society of American journalists who have been able to slip the meaningless and telltale phrase It was as if an occult hand had… in print as a sort of a game and inside joke. Contents 1 History 2… … Wikipedia

Помогите, пожалуйста, разобраться:
UTF-8 и UTF-8 без BOM - в чём разница в использовании? Что лучше использовать для сохранения файлов?

Когда-то у меня сложилось впечатление, что UTF-8 универсальнее, лучше использовать эту кодировку - тогда я имел дело с HTML, CSS +/- JavaScript, но позднее - имея дело с PHP - получил опыт, говорящий, что UTF-8 без BOM предпочтительнее (были проблемы, как раз, из-за UTF-8)

Так, как всё-таки быть? Что использовать?

Мой опыт пока такой: для клиентской части - UTF-8 (либо нет разницы), для серверной - UTF-8 без BOM - всё так? Почему?

delphinpro

Различий никаких нет, кроме наличия/отсутствия маркера. Кодировка одна и та же - utf-8. По стандарту unicode маркер должен быть.

Удалять маркер BOM при сохранении нужно только для PHP, который почему-то не умеет корректно обрабатывать нормальные unicode файлы.

littleguga

Маркер последовательности байтов или метка порядка байтов (англ. Byte Order Mark (BOM)) — Юникод-символ, используемый для индикации порядка байтов текстового файла. Его кодовый символ U+FEFF. По спецификации, его использование не является обязательным, однако, если маркер последовательности байтов используется, то он должен быть установлен в начале текстового файла. Помимо своего конкретного использования в качестве указателя порядка байтов, символ может также указать, какой кодировкой Unicode закодирован текст.

Кодировка Unicode может использовать 16-разрядные или 32-разрядные числа и приложение должно знать, как дальше с ними поступать. Поэтому потребность в маркере последовательности байтов возникает при обмене документами.

Если сохраняете php файл - то без BOM, в остальном же разницы никакой не имеет.

Наиболее важная концепция заключается в понимании разницы между числами и данными, которые эти числа представляют. Число — это абстрактное понятия, как исчислитель чего-то. У Вас есть десять пальцев. Понятие “десять” не меняется, в зависимости от использованного представления: десять, 10, diez (испанский), ju (японский), 1010 (бинарное представление), Х (римские числа)… Все эти представления указывают на понятие “десяти”.

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

Данные — это как человеческое письмо, просто набор отметок на бумаге. Этим отметкам не присуще какое-либо значение. Если мы видим линию и круг (например, |O), то можно интерпретировать это как “десять”. Но это лишь предположение, что считанные символы представляют число. Это могут быть буквы “IO” — название спутника Юпитера. Или, возможно, имя греческой богини. Или аббревиатура для ввода/вывода. Или чьи-то инициалы. Или число 2 в бинарном представлении (“10”). Этот список предположений можно продолжить. Дело в том, что один фрагмент данных (|O) может быть интерпретировано по разному, и смысл остается не ясен, пока кто-то не уточнит намерения автора.

Компьютеры сталкиваются с такой же проблемой. Они хранят данные, а не абстрактные понятия, используя при этом 1 и 0. Позднее они считывают эти 1 и 0 и пытаются воссоздать абстрактные понятия из набора данных. В зависимости от сделанных допущений, эти 1 и 0 могут иметь абсолютно разное значение.

Почему так происходит? Ну, вообще-то нет такого правила, что компьютеры должны использовать один и тот же язык, так же, как нет такого правила и для людей. Каждый компьютер одного типа имеет внутреннюю совместимость (он может считывать свои собственные данные), но нет никакой гарантии, как именно интерпретирует эти данные компьютер другого типа.

  • Данные (биты и байты или отметки на бумаге) сами по себе не имеют смысла. Они должны быть интерпретированы в какое-то абстрактное понятие, например, число.
  • Как и люди, компьютеры имеют различные способы хранения одного и того же абстрактного понятия (например, мы можем различными способами сказать “10”).

Храним числа как данные

  • Бит имеет два состояния (включен или выключен, 1 или 0).
  • Байт — это последовательность из 8 бит. Крайний левый бит в байте является старшим. То есть двоичная последовательность 00001001 является десятичным числом девять. 00001001 = (2^3 + 2^0 = 8 + 1 = 9).
  • Биты нумеруются справа налево. Бит 0 является крайним правым и он наименьший. Бит 7 является крайним левым и он наибольший.

Так в чем же проблема — компьютеры отлично ладят с одиночными байтами, правда? Ну, все превосходно для однобайтных данных, таких как ASCII-символы. Однако, много данных используют для хранения несколько байтов, например, целые числа или числа с плавающей точкой. И нет никакого соглашения о том, в каком порядке должны хранится эти последовательности.

Пример с байтом

Рассмотрим последовательность из 4 байт. Назовем их W X Y и Z. Я избегаю наименований A B C D, потому что это шестнадцатеричные числа, что может немного запутывать. Итак, каждый байт имеет значение и состоит из 8 бит.


Например, W — это один байт со значением 0х12 в шестнадцатеричном виде или 00010010 в бинарном. Если W будет интерпретироваться как число, то это будет “18” в десятеричной системе (между прочим, ничто не указывает на то, что мы должны интерпретировать этот байт как число — это может быть ASCII-символ или что-то совсем иное). Вы все еще со мной? Мы имеем 4 байта, W X Y и Z, каждый с различным значением.

Понимаем указатели

Указатели являются ключевой частью программирования, особенно в языке С. Указатель представляет собой число, являющееся адресом в памяти. И это зависит только от нас (программистов), как интерпретировать данные по этому адресу.

В языке С, когда вы кастите (приводите) указатель к конкретному типу (такому как char * или int *), это говорит компьютеру, как именно интерпретировать данные по этому адресу. Например, давайте объявим:


Обратите внимание, что мы не можем получить из р данные, потому что мы не знаем их тип. р может указывать на цифру, букву, начало строки, Ваш гороскоп или изображение — мы просто не знаем, сколько байт нам нужно считать и как их интерпретировать.

Теперь предположим, что мы напишем:


Этот оператор говорит компьютеру, что р указывает на то же место, и данные по этому адресу нужно интерпретировать как один символ (1 байт). В этом случае, с будет указывать на память по адресу 0, или на байт W. Если мы выведем с, то получим значение, хранящееся в W, которое равно шестнадцатеричному 0x12 (помните, что W — это полный байт). Этот пример не зависит от типа компьютера — опять же, все компьютеры одинаково хорошо понимают, что же такое один байт (в прошлом это было не всегда так).

Этот пример полезен, он одинаково работает на все компьютерах — если у нас есть указатель на байт (char *, один байт), мы можем проходить по памяти, считывая по одному байту за раз. Мы можем обратиться к любому месту в памяти, и порядок хранения байт не будет иметь никакого значения — любой компьютер вернет нам одинаковую информацию.

Так в чем же проблема?

Проблемы начинаются, когда компьютер пытается считать несколько байт. Многие типы данных состоят больше чем из одного байта, например, длинные целые (long integers) или числа с плавающей точкой. Байт имеет только 256 значений и может хранить числа от 0 до 255.

  • Машины с порядком хранения от старшего к младшему (прямой порядок) хранят старший байт первым. Если посмотреть на набор байтов, то первый байт (младший адрес) считается старшим.
  • Машины с порядком хранения от младшего к старшему (обратный порядок) хранят младший байт первым. Если посмотреть на набор байт, то первый байт будет наименьшим.

Повторюсь, порядок следования байтов не имеет значения пока Вы работаете с одним байтом. Если у Вас есть один байт, то это просто данные, которые Вы считываете и есть только один вариант их интерпретации (опять таки, потому что между компьютерами согласовано понятие одного байта).

Теперь предположим, что у нас есть 4 байта (WXYZ), которые хранятся одинаково на машинах с обоими типами порядка записи байтов. То есть, ячейка памяти 0 соответствует W, ячейка 1 соответствует X и т. д.

Мы можем создать такое соглашение, помня, что понятие “байт” является машинно-независимым. Мы можем обойти память по одному байту за раз и установить необходимые значения. Это будет работать на любой машине.


Такой код будет работать на любой машине и успешно установит значение байт W, X, Y и Z расположенных на соответствующих позициях 0, 1, 2 и 3.

Интерпретация данных

Теперь давайте рассмотрим пример с многобайтными данными (наконец-то!). Короткая сводка: “short int” это 2-х байтовое число (16 бит), которое может иметь значение от 0 до 65535 (если оно беззнаковое). Давайте используем его в примере.

  • Машина с прямым порядком хранения: Я думаю, short int состоит из двух байт, а значит я считаю их. Позиция s это адрес 0 (W или 0х12), а позиция s + 1 это адрес 1 (X или 0х34). Поскольку первый байт является старшим, то число должно быть следующим 256 * байт 0 + байт 1 или 256 * W + X, или же 0х1234. Я умножаю первый байт на 256 (2^8) потому что его нужно сдвинуть на 8 бит.
  • Машина с обратным порядком хранения: Я не знаю что курит мистер “От старшего к младшему”. Я соглашусь, что short int состоит из 2 байт и я считаю их точно также: позиция s со значение 0х12 и позиция s + 1 со значением 0х34. Но в моем мире первым является младший байт! И число должно быть байт 0 + 256 * байт 1 или 256 * X + W, или 0х3412.

Теперь Вы видите проблему? Машина с порядком хранения от старшего к младшему считает, что s = 0x1234, в то время как машина с порядком хранения от младшего к старшему думает, что s = 0x3412. Абсолютно одинаковые данные дают в результате два совершенно разных числа.

И еще один пример

Давайте для “веселья” рассмотрим еще один пример с 4 байтовым целым:

  • Машина с прямым порядком хранения: тип int состоит из 4 байт и первый байт является старшим. Считываю 4 байта (WXYZ) из которых старший W. Полученное число: 0х12345678.
  • Машина с обратным порядком хранения: несомненно, int состоит из 4 байт, но старшим является последний. Так же считываю 4 байта (WXYZ), но W будет расположен в конце — так как он является младшим. Полученное число: 0х78563412.

Проблема NUXI

Проблему с порядком байт иногда называют проблемой NUXI: слово UNIX, сохраненное на машинах с порядком хранения от старшего к младшему, будет отображаться как NUXI на машинах с порядком от младшего к старшему.

Допустим, что мы собираемся сохранить 4 байта (U, N, I, и X), как два short int: UN и IX. Каждая буква занимает целый байт, как в случае с WXYZ. Для сохранения двух значений типа short int напишем следующий код:


Этот код не является специфичным для какой-то машины. Если мы сохраним значение “UN” на любой машине и считаем его обратно, то обратно получим тоже “UN”. Вопрос порядка следования байт не будет нас волновать, если мы сохраняем значение на одной машине, то должны получить это же значение при считывании.

Однако, если пройтись по памяти по одному байту за раз (используя трюк с char *), то порядок байт может различаться. На машине с прямым порядком хранения мы увидим:


Что имеет смысл. “U” является старшим байтом в “UN” и соответственно хранится первым. Такая же ситуация для “IX”, где “I” — это старший байт и хранится он первым.

На машине с обратным порядком хранения мы скорее всего увидим:


Но и это тоже имеет смысл. “N” является младшим байтом в “UN” и значит хранится он первым. Опять же, хотя байты хранятся в “обратном порядке” в памяти, машины с порядком хранения от младшего к старшему знают что это обратный порядок байт, и интерпретирует их правильно при чтении. Также, обратите внимание, что мы можем определять шестнадцатеричные числа, такие как 0x1234, на любой машине. Машина с обратным порядком хранения байтов знает, что Вы имеете в виду, когда пишите 0x1234 и не заставит Вас менять значения местами (когда шестнадцатеричное число отправляется на запись, машина понимает что к чему и меняет байты в памяти местами, скрывая это от глаз. Вот такой трюк.).

Рассмотренный нами сценарий называется проблемой “NUXI”, потому что последовательность “UNIX” интерпретируется как “NUXI” на машинах с различным порядком хранения байтов. Опять же, эта проблема возникает только при обмене данными — каждая машина имеет внутреннюю совместимость.

Обмен данными между машинами с различным порядком хранения байтов

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

Решение 1: Использовать общий формат

Самый простой подход состоит в согласовании с общим форматом для передачи данных по сети. Стандартным сетевым является порядок от старшего к младшему, но некоторые люди могут расстроиться, что не победил порядок от младшего к старшему, поэтому просто назовем его “сетевой порядок”.

Для конвертирования данных в соответствии с сетевым порядком хранения байтов, машины вызывают функцию hton() (host-to-network). На машинах с прямым порядком хранения эта функция не делает ничего, но мы не будем говорить здесь об этом (это может разозлить машины с обратным порядком хранения :) ).

Но важно использовать функцию hton() перед отсылкой данных даже если Вы работаете на машине с порядком хранения от старшего к младшему. Ваша программа может стать весьма популярной и будет скомпилирована на различных машинах, а Вы ведь стремитесь к переносимости своего кода (разве не так?).

Точно также существует функция ntoh() (network-to-host), которая используется для чтения данных из сети. Вы должны использовать ее, чтобы быть уверенными, что правильно интерпретируете сетевые данные в формат хоста. Вы должны знать тип данных, которые принимаете, чтобы расшифровать их правильно. Функции преобразования имеют следующий вид:


Помните, что один байт — это один байт и порядок не имеет значения.

Эти функции имеют критическое значение при выполнении низкоуровневых сетевых операций, таких как проверка контрольной суммы IP-пакетов. Если Вы не понимаете сути проблемы с порядком хранения байтов, то Ваша жизнь будет наполнена болью — поверьте мне на слово. Используйте функции преобразования и знайте, зачем они нужны.

Решение 2: Использования маркера последовательности байтов (Byte Order Mark — BOM)

Этот подход подразумевает использование некого магического числа, например 0xFEFF, перед каждым куском данных. Если Вы считали магическое число и его значение 0xFEFF, значит данные в том же формате, что и у Вашей машины и все хорошо. Если Вы считали магическое число и его значение 0xFFFE, это значит, что данные были записаны в формате, отличающемся от формата вашей машины и Вы должны будете преобразовать их.

Нужно отметить несколько пунктов. Во-первых, число не совсем магическое, как известно программисты часто используют этот термин для описания произвольно выбранных чисел (BOM может быть любой последовательностью различных байтов). Такая пометка называется маркером последовательности байтов потому что показывает в каком порядке данные были сохранены.

Во-вторых, BOM добавляет накладные расходы для всех передаваемых данных. Даже в случае передачи 2 байт информации Вы должны добавлять к ним 2 байта маркера BOM. Пугающе, не так ли?

Unicode использует BOM, когда сохраняет многобайтные данные (некоторые кодировки Unicode могут иметь по 2, 3 и даже 4 байта на символ). XML позволяет избежать этой путаницы, сохраняя данные сразу в UTF-8 по умолчанию, который сохраняет информацию Unicode по одному байту за раз. Почему это так круто?

Повторяю в 56-й раз — потому что проблема порядка хранения не имеет значения для единичных байт.

Опять же, в случае использования BOM может возникнуть другие проблемы. Что, если Вы забудете добавить BOM? Будете предполагать, что данные были отправлены в том же формате, что и Ваши? Прочитаете данные и, увидев что они “перевернуты” (что бы это не значило), попытаетесь преобразовать их? Что, если правильные данные случайно будут содержать неправильный BOM? Эти ситуации не очень приятные.

Почему вообще существует эта проблема? Нельзя ли просто договориться?

Ох, какой же это философский вопрос. Каждый порядок хранения байтов имеет свои преимущества. Машины с порядком следования от младшего к старшему позволяют читать младший байт первым, не считывая при этом остальные. Таким образом можно легко проверить является число нечетным или четным (последний бит 0), что очень здорово, если Вам необходима такая проверка. Машины с порядком от старшего к младшему хранят данные в памяти в привычном для человека виде (слева направо), что упрощает низкоуровневую отладку.

Так почему же все просто не договорятся об использовании одной из систем? Почему одни компьютеры пытаются быть отличными от других? Позвольте мне ответить вопросом на вопрос: почему не все люди говорят на одном языке? Почему в некоторых языках письменность слева направо, а у других справа налево?

Иногда системы развиваются независимо, а в последствии нуждаются во взаимодействии.

Эпилог: Мысли на прощание


Вопросы с порядком хранения байтов являются примером общей проблемы кодирования — данные должны представлять собой абстрактные понятия, и позднее это понятие должно быть создано из данных. Эта тема заслуживает отдельной статьи (или серии статей), но Вы должны иметь лучшее понимание проблемы, связанной с порядком хранения байтов.

Конечно, о юникоде (Unicode) в интернете море информации (см, например Юникод, некоторые фрагменты текста я взял оттуда), я ни в коей мере не претендую на полное и исчерпывающее описание. Это просто некоторая дополнительная «информация к размышлению».

В предыдущей заметке О кодировках и кодовых страницах, я писал о проблемах показа однобайтовых символов. Суть в том, что хотя для показа 26 символов латинского алфавита достаточно 52 кодов (прописные+строчные), для показа национальных символов даже 256 кодов (это байт, единица хранения и адресации) оказывается недостаточно. Для решения проблемы отображения национальных символов использовались (да и сейчас кое-где используются) кодовые страницы. Хочешь выводить национальные символы – используй соответствующую кодовую страницу. Но что, если в тексте нужны символы из многих кодовых страниц? Для набора математического текста могут понадобиться английские, русские, математические, греческие, типографские символы, причем одновременно. Кроме того, для национальных символов используется несколько конкурирующих кодировок (т.е. кодовых страниц), какую выбрать? Решить эти проблемы должен был созданный в 1991 году стандарт Unicode (по-русски Юникод или Уникод).

Юникод решительно порвал с однобайтовым прошлым и предложил стандарт UCS-2 (universal character set), где каждый символ кодируется раз и навсегда закрепленным за ним 16-ти битовым числом, состоящим из старшего байта и младшего байта (естественно, для прописных и строчных символов – разные числа). Кроме кода, за каждым символом закреплено название (на английском языке). Скажем, за русской прописной “A” закреплен код 104010, а за строчной “я” закреплен код 110310 и название «CYRILLIC SMALL LETTER YA». Здесь коды чисел приведены в 10-ричной системе счисления, но часто используют 16-ричной коды: 104010 соответствует 41016, а коду 110310 соответствует 44F16. Дабы не гадать, в какой системе счисления записан код и чтобы сразу было ясно, что речь идет не об абстрактном числе, а о коде символа юникода, используется особая запись: U+0410 для “А” и U+044F для “я”. Видим в тесте U+FEFF и сразу понимаем, что речь идет о символе юникода FEFF16, он же 6527910 (по-русски называется «неразрывный пробел нулевой ширины»). А коды U+0401 и U+0451 назначены символам Ё «CYRILLIC CAPITAL LETTER IO» и ё соотвественно (букву Ё опять «обидели», присвоив ей коды вне диапазона остальных букв русского алфавита). Все символы кириллицы (да и многие другие вместе с кодами) очень удобно смотреть на Кириллица.

Итак, Unicode перешел от однобайтовой кодировке к 2-байтовой, кардинально расширил количество описываемых символов – можно почивать на лаврах? Отнюдь, с новыми возможностями пришли новые проблемы. Проблема первая – как хранить эти 2 байта символа в памяти и в файлах? Запишем код U+0410 в виде двух байтов: 04(старший) и 10(младший). В памяти, как мы знаем, каждый байт имеет свой адрес и можно записать наш код двумя способами: либо 04 по адресу X, а 10 по адресу X+1, либо 10 по адресу X, а 04 по адресу X+1. Соответственно, в файле 10 будет записано либо после 04 либо перед ним. На первый взгляд проблема высосана из пальца: раз код юникода – это 16-битовое число, давайте и хранить его так, как компьютер хранит 16-битовые числа. А-а-а, вот тут и попались – компьютеры различной архитектуры хранят 16-ти битовые числа по-разному: одни хранят старший байт числа перед младшим, а другие — после! Персоналки хранят старший байт после младшего, а многие «большие компьютеры» хранят старший байт перед младшим. Архитектура многих смартфонов такая, что данные хранятся, «как в больших компьютерах», так что для архитектур «размер не имеет значения». Когда данные пишутся из памяти в файл или читаются из файла в память (напрямую, без участия процессора), байты передаются в порядке возрастания адресов. С другой стороны, операции сравнения и сортировки требуют работы с кодами как с числами. В общем – либо проблемы с порядком байтов в файлах, либо проблемы с алгоритмами обработки. Победили алгоритмы и 2 байта символа хранятся в памяти так же, как данная архитектура хранит целые числа (т.е. код «в смысле юникода» и код как число в памяти – это одно и то же 16-ти битовое число, независимо от архитектуры). Соответственно, в файле первым идет либо старший байт числа (стандарт UCS-2BE – “Big Endian” или «прямой порядок»), либо младший байт (стандарт UCS-2LE – “Little Endian” или «обратный порядок»).

Ну хорошо, все же 2 варианта хранения – это не несколько десятков кодовых страниц. Подождите – еще не вечер, обсудили только первую проблему. По мере роста популярности юникода возникло масса желающих добавить в него свои символы. Ладно бы хотели добавить символы типа церковнославянских «ять», «ижица», но появилось желание добавить тысячи иероглифов, знаков древних письменностей и тп. В общем, 65536 символов не хватило (а ведь когда-то казалось «всем-всем хватит», были даже незанятые области, «про запас»). В результате в 1996 году появился второй стандарт юникода, расширяющий количество доступных символов с 65536 до 1’112’064, так что код символа стал от U+0000 до U+10FFFF. Расширили «с большим запасом» и даже спустя 16 лет, в стандарте юникода 6.2 от 2012г описано ~110000 символов (плюс ~137000 зарезервировано). Расширить расширили, но как их хранить в памяти, в файлах? Для такого количество символов нужны числа с 21 битом, т.е. 3 байта. И что делать с морем программ, файлов которые хранят и обрабатывают 2-х байтовые символы юникода стандарта USC-2?

В результате нашли компромиссное решение, снижающее сложности перехода. Поступили так: те 65536 кодов из UCS-2 – они наиболее употребительные, их трогать не будем и храним как раньше – в виде 16-ти битового числа (этот набор кодов назвали нулевой или базовой плоскостью). Остальные коды образуют плоскости с 1-й по 16-ю (в каждой по 65536 кодов). Так что символы нулевой плоскости U+0000 – U+FFFF храним как раньше – в виде 16-битового числа, а символы с кодами U+010000 — U+10FFFF (таких кодов 220) храним в виде пары 16-ти битных чисел (так называемые «суррогатные пары»), первое число пары из диапазона U+D800 — U+DBFF, а второе — из диапазона U+DС00 — U+DFFF. Легко увидеть, что в каждом диапазоне 10 бит произвольны, в паре это дает произвольное 20-ти битовое число. Но как отличить, представляет ли код U+DA15 соответствующий ему символ из UCS-2 или это первый код суррогатной пары? А не надо отличать – в UCS-2 коды из диапазона D800 — DFFF (2048 символов) были зарезервированы, поэтому им не соответствовали никакие символы. Стандарты представления символов с «суррогатными парами» называются utf-16BE и utf-16LE. Пришлось, конечно, переписывать программы и библиотеки для работы с новым стандартом, но если не было нужды в использовании символов из плоскостей 1-16, то и старые программы отлично работали. Java (которая сразу использовала стандарт UCS-2) включила поддержку суррогатных пар только в версии J2SE 5.0, а до этого как-то обходилась без них… Почему в суррогатных парах диапазоны первого и второго символа различаются? Не знаю… Если бы был один общий диапазон, то тогда бы можно было закодировать не 20 бит, а 22 бита и вместо миллиона доступных символов получить 4 миллиона. Но миллион тоже много, когда еще его «истратят», а раздельные диапазоны дают дополнительный контроль (вдруг файл не юникодовский).

Резюмирую: для символов U+0000-U+FFFF в utf-16 используются 16-битовое представление, как в UCS-2, а для символов U+10000-U+10FFFF в используются «суррогатная пара» из кодов в области U+D800-U+DFFF (перед переводом числа U+10000 и выше его уменьшают на 1000016 и полученное 20-битное число кодируют суррогатной парой). Итого, в utf-16 можно представить 2 20 +2 16 -2048 = 1’112’064 символов. Что дало расширение Unicode? Те, кому нужны были новые символы, получили возможность их использовать. Те, кому не нужны редко используемые иероглифы, кто не работает с устаревшей письменностью расширения возможностей [почти] не заметили. А вот программистам мороки добавилось. То ли дело в UCS-16: хочешь получить n-й символ строки, берешь n-й символ массива и все. А с суррогатными парами это не проходит. Пишешь программу, работающую с текстом – не забывай про эти «пары» (даже если тебе они тебе вроде как и не нужны, все равно используй другие объекты, другие функции).

Есть ли другие способы преставления (хранения) символов юникода, не utf-16? Есть – это utf-32, где каждый символ представлен 32-битным числом. Разумеется, старший байт такого числа всегда 0, а байт перед ним почти всегда 0, но столь неэкономное расходование памяти компенсируется удобством обработки (не нужны суррогатные пары, нет проблемы «взять n-й символ в строке). Естественно, в зависимости от архитектуры, используют вариант либо utf-32BE либо utf-32LE).

А теперь сюрприз — кроме utf-16/32 есть еще одно замечательное представление символов юникода в виде последовательности байтов (от 1 до 4). Это представление придумали в 1992 году Кен Томпсон и Роб Пайк и назвали его utf-8. В этом представлении символам ASCII (первые 128 символов 0-й плоскости) соответствует сам код символа, т.е. текст из символов с номером меньше 128 в utf-8 состоит из тех же самых байтов, поэтому любую строку в ASCII автоматически можно считать строкой в utf-8.

Символы utf-8 получаются из Unicode следующим образом (из Википедии):
0x00000000 — 0x0000007F: 0xxxxxxx (т.о. символы 0-127 не меняются)
0x00000080 — 0x000007FF: 110xxxxx 10xxxxxx
0x00000800 — 0x0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
0x00010000 — 0x001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Чем больше код символа, тем больше надо байтов для его преставления в utf-8. Счастливым пользователям «чисто латинского» алфавита достаточно 1 байта (так что выигрыш по сравнению с utf-16 очевиден), европейцам только иногда требуется два байта. А для кириллицы всегда надо два байта (как в utf-16), но для смешанного русско-английского текста какой-то выигрыш получается. А вот сирийским, грузинским символам надо три байта… Еще один плюс utf-8 — в компьютерах с любой архитектурой байты хранятся по возрастанию адресов и поэтому не нужны BE/LE (суррогатные пары, кстати, тоже не нужны). Наконец, такая фишка utf-8, как «самосинхронизация». Предположим, программа принимает поток символов в формате utf-8 и выводит их на экран. Пусть по каким-то причинам (сбой, например) программа неверно определила номер байта для принимаемого символа. Естественно, на экран вместо правильного символа будет выведен совсем другой символ. Но очень быстро программа исправится и начнет выводить корректные символы (это не свойство программы – это свойство самой utf-8). А если бы принимали utf-16/32 и потеряли байт (или вместо ожидаемого utf-16LE поступает utf-16BE)? Все, приехали – вместо нужной информации на экране будут «не наши» символы и для правильного показа программа должна сделать какой-то интеллектуальный анализ.

Кстати, раз уж пошла речь о сбоях и чужих форматах, как программа определит, в каком именно utf записан файл (даже если она точно знает, что имеет дело с юникодом)? Вариантов три:
1. В самое начало файла записан специальный символ с кодом U+FEFF. Позвольте, ведь это «неразрывный пробел нулевой ширины». Да, это он, но теперь как пробел он не используется и у него есть второе имя –“byte order mark” (BOM, маркер порядка байтов). В файл utf-16LE он запишется как FF FE, а в файл utf-16BE как FE FF, поэтому, прочитав пару (тройку) первых байт файла, программа может определить, в каком формате записан файл. А вдруг в файл записан символ с кодом U+FFFE и программа собьется, приняв его за BOM? Не беспокойтесь – символа с кодом U+FFFE не существует, т.е. такой код никому не назначен (и не будет). Для файлов в формате utf-8 первым в файле тоже может быть записан символ U+FEFF (он выглядит как последовательность EF BB BF), хотя здесь он маркирует не порядок байтов, а сам формат utf-8. А если программа старенькая и про новую роль BOM ничего не знает, а принимает его за «неразрывный пробел нулевой ширины»? Ничего страшного – он «пробел» и поэтому пустой, а из-за «нулевой ширины» места на экране не занимает, т.е. на экране его не видно (код специально так подобрали, чтобы не имел пары с обратным порядком байтов и не «мозолил» глаза при выводе)! А его «типографскую роль» сначала взял на себя символ “word joiner” U+2060, а потом и другие (U+200b “zero width space”, U+200d “zero width joiner”)
2. Есть соглашение, что этот файл всегда пишется в определенном формате или есть общесистемное соглашение (к примеру, в персоналках и windows – LE, в «больших» машинах и unix — BE)
3. Если нет ни BOM ни соглашения, то для межплатформенного обмена считается, что файл в стандарте utf-16BE (тут unix и большие машины «победили» windows и персоналки).

Всегда добавляйте префикс к текстовому файлу Юникода с меткой порядка байтов, которая информирует приложение о получении файла о том, что файл является упорядоченным по байтам. Доступные метки порядка байтов перечислены в следующей таблице. Поскольку обычный текст в Юникоде представляет собой последовательность из 16-разрядных значений кода, он учитывает порядок байтов, используемый при написании текста.

Метка порядка байтов не является управляющим символом, который выбирает порядок байтов текста.

Метка порядка байтов Описание
EF BB BF UTF-8
FF FE UTF-16, с прямым порядком байтов
FE FF UTF-16, обратный порядок байтов
FF FE 00 00 UTF-32, с прямым порядком байтов
00 00 FE FF UTF-32, с обратным порядком байтов

Корпорация Майкрософт использует UTF-16 с прямым порядком байтов.

В идеале весь текст в Юникоде следует только одному набору правил порядка следования байтов. Однако это невозможно, поскольку микропроцессоры отличаются размещением наименее значимого байта. Процессоры Intel и MIPS сначала размещаются на наименее значащий байт, тогда как процессоры Motorola (и все файлы в формате Юникод с обратным байтом) располагают его последними. При использовании только одного набора правил порядка байтов пользователи одного типа микропроцессора вынуждены менять порядок байтов каждый раз, когда текстовый файл считывается или записывается, даже если файл никогда не передается в другую операционную систему на основе другого микропроцессора.

Предпочтительное место для указания порядка байтов находится в заголовке файла, но текстовые файлы не имеют заголовков. Поэтому в Юникоде определен символ (U + FEFF) и несимвольный (U + ФФФЕ) в качестве меток порядка байтов. Они представляют собой зеркально отображаемые байтовые изображения.

Поскольку последовательность U + FEFF превышена в начале обычного текстового файла в формате, отличном от Юникода, она может служить неявным маркером или сигнатурой для того, чтобы идентификатор файла определялся как файл Юникода. Приложения, считывающие текстовые файлы в Юникоде и не в Юникоде, должны использовать эту последовательность в качестве индикатора того, что файл, скорее всего, является файлом Юникода. Сравните этот метод с использованием маркера MS-DOS EOF для завершения текстовых файлов.

Когда приложение обнаруживает U + FEFF в начале текстового файла, оно обычно обрабатывает файл как файл Юникода, хотя он может выполнять дальнейшие эвристические проверки. Такая проверка может быть настолько простой, как тестирование, чтобы выяснить, является ли изменение в байтах нижнего порядка большим, чем изменение в байтах с высоким порядковым номером. Например, если текст ASCII преобразуется в текст в Юникоде, каждый второй байт равен 0. Кроме того, при проверке того, что символы перевода строки и возврата каретки (U + 000A; и U + 000D), а также для четного или нечетного размера файла, могут дать строгий показатель природы файла.

Когда приложение обнаруживает U + ФФФЕ в начале текстового файла, оно интерпретирует его как файл с обратным байтом в Юникоде. Приложение может либо поменять порядок байтов, либо предупредить пользователя о том, что произошла ошибка.

Поскольку символ метки порядка байтов Юникода не найден ни в одной кодовой странице, он исчезает, если данные преобразуются в ANSI. В отличие от других символов Юникода, он не заменяется символом по умолчанию при преобразовании. Если метка порядка байтов находится в середине файла, она не интерпретируется как символ Юникода и не влияет на вывод текста.

Значение Юникода U + FFFF недопустимо в текстовых файлах и не может передаваться между приложениями. Он зарезервирован для частного использования приложения.

Представление кодировки byte order marks

Encoding Representation (hexadecimal) Representation (decimal) Representation (ISO-8859-1)
UTF-8 [t 1] EF BB BF 239 187 191 
UTF-16 (BE) FE FF 254 255 þÿ
UTF-16 (LE) FF FE 255 254 ÿþ
UTF-32 (BE) 00 00 FE FF 0 0 254 255 □□þÿ (□ is the ascii null character)
UTF-32 (LE) FF FE 00 00 255 254 0 0 ÿþ□□ (□ is the ascii null character)
UTF-7 [t 1] 2B 2F 76 38
2B 2F 76 39
2B 2F 76 2B
2B 2F 76 2F [t 2]
43 47 118 56
43 47 118 57
43 47 118 43
43 47 118 47
+/v8
+/v9
+/v+
+/v/
UTF-1 [t 1] F7 64 4C 247 100 76 ÷dL
UTF-EBCDIC [t 1] DD 73 66 73 221 115 102 115 Ýsfs
SCSU [t 1] 0E FE FF [t 3] 14 254 255 □þÿ (□ is the ascii "shift out" character)
BOCU-1 [t 1] FB EE 28 251 238 40 ûî
GB-18030 [t 1] 84 31 95 33 132 49 149 51 □1■3 (□ and ■ are unmapped ISO-8859-1 characters)

Использование

Если символ спецификации появится в середине потока данных, Unicode говорит, что это должно быть истолковано как «нулевой ширины неразрывный пробел» (по существу нулевой символ). В Unicode 3.2, это использование не рекомендуется использовать в пользу «Word Joiner», символа , U+2060. Это позволяет U+FEFF использоваться только в качестве спецификации.

Полезное

Читайте также: