UTF-16 фиксированной или переменной ширины? Почему у UTF-8 нет проблемы порядка следования байтов?

16
  1. UTF-16 фиксированной или переменной ширины? Я получил разные результаты из разных источников:

    С http://www.tbray.org/ongoing/When/200x/2003/04/26/UTF :

    UTF-16 хранит символы Unicode в шестнадцати разрядных блоках.

    С http://en.wikipedia.org/wiki/UTF-16/UCS-2 :

    UTF-16 (16-битный формат преобразования Unicode) - это кодировка символов для Unicode, способная кодировать 1 112 064 [1] числа (называемых кодовыми точками) в кодовом пространстве Unicode от 0 до 0x10FFFF. Он выдает результат переменной длины, состоящий из одного или двух 16-битных кодовых блоков на кодовую точку.

  2. Из первого источника

    UTF-8 также имеет преимущество в том, что единицей кодирования является байт, поэтому нет проблем с порядком байтов.

    Почему у UTF-8 нет проблемы порядка следования байтов? Это переменная ширина, и один символ может содержать более одного байта, поэтому я думаю, что порядок следования байтов все еще может быть проблемой?

Спасибо и всего наилучшего!

StackExchange для всех
источник

Ответы:

13

(1) Что означает последовательность байтов, набор символов в C? Является ли UTF-16 байтовой последовательностью или что это тогда? (2) Почему последовательность байтов не имеет ничего общего с переменной длиной?

Похоже, вы неправильно понимаете, что такое порядковый номер. Вот краткое резюме.

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

uint32_t value = 0x8100FF32;
uint8_t highByte = (uint8_t)((value >> 24) & 0xFF); //Now contains 0x81

Это все хорошо. Проблема начинается с того, как различное оборудование хранит и извлекает целые числа из памяти.

В порядке с прямым порядком байтов 4-байтовый фрагмент памяти, который вы читаете как 32-разрядное целое число, будет прочитан, причем первый байт будет старшим байтом:

[0][1][2][3]

В порядке Little Endian 4-байтовый фрагмент памяти, который вы читаете как 32-разрядное целое число, будет читаться с первым байтом, являющимся младшим байтом:

[3][2][1][0]

Если у вас есть указатель на указатель на 32-битное значение, вы можете сделать это:

uint32_t value = 0x8100FF32;
uint32_t *pValue = &value;
uint8_t *pHighByte = (uint8_t*)pValue;
uint8_t highByte = pHighByte[0]; //Now contains... ?

Согласно C / C ++, результат этого не определен. Это может быть 0x81. Или это может быть 0x32. Технически, он может вернуть все что угодно, но для реальных систем он вернет одну или другую.

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

Обратите внимание, что это все о чтении и записи в / из памяти. Это не имеет ничего общего с внутренним кодом C / C ++. Первая версия кода, которую C / C ++ не объявляет как неопределенную, всегда будет работать для получения старшего байта.

Проблема в том, когда вы начинаете читать потоки байтов. Например, из файла.

16-битные значения имеют те же проблемы, что и 32-битные; у них просто 2 байта вместо 4. Следовательно, файл может содержать 16-битные значения, хранящиеся в порядке с прямым или прямым порядком байтов.

UTF-16 определяется как последовательность 16-битных значений . По сути, это uint16_t[]. Каждая единица кода является 16-битным значением. Следовательно, для правильной загрузки UTF-16 вы должны знать, что такое порядок данных.

UTF-8 определяется как последовательность 8-битных значений . Это uint8_t[]. Каждая единица кода имеет размер 8 битов: один байт.

Теперь, как UTF-16 и UTF-8 позволяют несколько блоков кода (16-битные или 8-разрядные значения) , чтобы объединить вместе , чтобы сформировать элемент кода Unicode (а «символ», но это не правильный термин, это упрощение ). Порядок этих кодовых блоков , которые образуют элемент кода диктуется UTF-16 и UTF-8 кодировке.

При обработке UTF-16 вы читаете 16-битное значение, выполняя любое обратное преобразование. Затем вы обнаруживаете, если это суррогатная пара; если это так, тогда вы читаете еще одно 16-битное значение, объединяете их, и из этого вы получаете значение кодовой точки Unicode.

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

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

Николь Болас
источник
10

Ответ Джереми Бэнкса верен, но не касается порядка байтов.

Когда вы используете UTF-16, большинство глифов хранятся с использованием двухбайтового слова - но когда слово хранится в файле на диске, какой порядок вы используете для хранения составляющих байтов?

Например, глиф CJK (китайский) для слова «вода» имеет кодировку UTF-16 в шестнадцатеричном формате 6C34. Когда вы записываете это как два байта на диск, вы записываете это как "big-endian" (два байта - 6C 34)? Или вы пишете это как "little-endian (два байта 34 6C)?

В UTF-16 оба порядка являются законными, и вы обычно указываете, какой из файлов имеет файл, делая первое слово в файле меткой порядка байтов (BOM), которая для кодирования с прямым порядком байтов равна FE FF, а для байтов с прямым порядком байтов кодировка FF FE.

UTF-32 имеет ту же проблему и то же решение.

UTF-8 не имеет этой проблемы, потому что это переменная длина, и вы эффективно пишете последовательность байтов глифа, как если бы она была с прямым порядком байтов. Например, буква «P» всегда кодируется с использованием одного байта - 80 -, а символ замены всегда кодируется с использованием двухбайтовой FF FD в этом порядке.

Некоторые программы помещают трехбайтовый индикатор (EF BB BF) в начало файла UTF-8, и это помогает отличить UTF-8 от аналогичных кодировок, таких как ASCII, но это не очень распространено, за исключением MS Windows.

Боб Мерфи
источник
Благодарность! (1) буква «P» - это всего один байт в UTF-8. Почему символ замены добавляется в его код? (2) В UTF-8 есть другие символы, которые имеют более одного байта в UTF-8. Почему порядок байтов между байтами для каждого такого символа не является проблемой?
StackExchange для всех
@Tim: (1) Вы не добавляете символ замены в код для P. Если вы видите 80 FF FD, это два символа - символ P и символ замены.
Боб Мерфи
(2) Вы всегда пишете и читаете два байта для «символа замены» как FF FD, в этом порядке. Возникла бы проблема с порядком байтов, если бы вы также могли написать «символ замены» как FD FF - но вы не можете; эта последовательность из двух байтов будет чем-то отличным от «символа замены».
Боб Мерфи
1
@Tim: Возможно, вы захотите поработать через en.wikipedia.org/wiki/UTF-8 . Это действительно неплохо, и если вы сможете понять все это и другие страницы Википедии, связанные с Unicode, я думаю, вы обнаружите, что у вас больше нет вопросов по этому поводу.
Боб Мерфи
4
Причина того, что UTF-8 не имеет проблем с порядком байтов, заключается в том, что кодирование определяется как последовательность байтов и что нет изменений с другим порядком байтов. Это не имеет ничего общего с переменной длиной.
звездный синий