UTF-8

Материал из Википедии — свободной энциклопедии
Версия от 13:23, 25 февраля 2026; imported>Felex67 (дополнение: пример кода на языке C.)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)
Перейти к навигации Перейти к поиску

UTF-8 (от англ. Шаблон:Lang-en2 — «формат преобразования Юникода, 8-бит») — распространённый стандарт кодирования символов, позволяющий более компактно хранить и передавать символы Юникода, используя переменное количество байт (от 1 до 4), и обеспечивающий полную обратную совместимость с 7-битной кодировкой ASCII. Стандарт UTF-8 официально закреплён в документах RFC 3629 и ISO/IEC 10646 Annex D.

Кодировка UTF-8 сейчас является доминирующей в веб-пространстве. Она также нашла широкое применение в UNIX-подобных операционных системах<ref>Usage Statistics of Character Encodings for Websites, June 2011Шаблон:Ref</ref>.

Формат UTF-8 был разработан 2 сентября 1992 года Кеном Томпсоном и Робом Пайком, и реализован в Plan 9<ref>Шаблон:Cite web</ref>. Идентификатор кодировки в Windows — 65001<ref>Шаблон:Cite web</ref>.

UTF-8, по сравнению с UTF-16, наибольший выигрыш в компактности даёт для текстов на латинице, поскольку латинские буквы без диакритических знаков, цифры и наиболее распространённые знаки препинания кодируются в UTF-8 лишь одним байтом, и коды этих символов соответствуют их кодам в ASCII.<ref name="stringtheory"> Шаблон:Cite web </ref><ref name="vsem"> Шаблон:Cite web</ref>

Алгоритм кодирования (сериализации)

Алгоритм кодирования в UTF-8 стандартизирован в RFC 3629 и состоит из 3 этапов:

1. Определить количество октетов, требуемых для кодирования символа. Номер символа берётся из стандарта Юникода.

Диапазон номеров символов Требуемое количество октетов
00000000-0000007F 1
00000080-000007FF 2
00000800-0000FFFF 3
00010000-0010FFFF 4

Для символов Юникода с номерами от U+0000 до U+007F (занимающими один октет c нулём в старшем бите) кодировка UTF-8 полностью соответствует 7-битной кодировке US-ASCII.

2. Установить старшие биты первого октета в соответствии с необходимым количеством октетов для кодирования символа, определённом на первом этапе:

  • 0xxxxxxx — если для кодирования потребуется один октет;
  • 110xxxxx — если для кодирования потребуется два октета;
  • 1110xxxx — если для кодирования потребуется три октета;
  • 11110xxx — если для кодирования потребуется четыре октета.

Если для кодирования требуется больше одного октета, то в октетах 2-4 два старших бита всегда устанавливаются равными 102 (10xxxxxx). Это позволяет легко отличать первый октет в потоке, от других октетов

Количество октетов Значащих бит Шаблон
1 7 0xxxxxxx
2 11 110xxxxx 10xxxxxx
3 16 1110xxxx 10xxxxxx 10xxxxxx
4 21 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

3. Установить значащие биты октетов в соответствии с номером символа Юникода, выраженном в двоичном виде. Начать заполнение с младших битов номера символа, поставив их в младшие биты последнего октета, продолжить справа налево до первого октета. Свободные биты первого октета, оставшиеся незадействованными, заполнить нулями.

Примеры кодирования

Символ Двоичный код символа UTF-8 в двоичном виде UTF-8 в шестнадцатеричном виде
$ U+0024 Шаблон:Color 0Шаблон:Color Шаблон:Color
¢ U+00A2 Шаблон:ColorШаблон:Color 110Шаблон:Color 10Шаблон:Color Шаблон:Color Шаблон:Color
U+20AC Шаблон:ColorШаблон:Color Шаблон:ColorШаблон:Color 1110Шаблон:Color 10Шаблон:Color 10Шаблон:Color Шаблон:Color Шаблон:Color Шаблон:Color
𐍈 U+10348 Шаблон:ColorШаблон:ColorШаблон:Color 11110Шаблон:Color 10Шаблон:Color 10Шаблон:Color 10Шаблон:Color Шаблон:Color Шаблон:Color Шаблон:Color Шаблон:Color

Пример кодирования Unicode-строки в UTF-8 без предварительного просчёта длины на языке C

<syntaxhighlight lang="c" line="1">

  1. ifdef _WIN32

typedef unsigned __int8 u_int8_t; typedef unsigned __int16 u_int16_t; typedef unsigned __int32 u_int32_t; typedef unsigned __int64 u_int64_t; typedef wchar_t uchar_t;

  1. else

#include <sys/types.h> typedef u_int16_t uchar_t;

  1. endif

size_t UnicodeToUTF8(char** pDest, const uchar_t* Src, size_t SrcLen) { size_t dlen = 0; // Длина целевой строки char* offset = *pDest = NULL // Итератор по целевой строке , header = (char)0x80 // "заголовок" первого байта , fcap = 0b00111111; // Вместимость первого бийта const uchar_t* src = Src // указатель на кодируемую точку , * end = Src + SrcLen; // Указатель на конец исходной строки uchar_t code = 0; // Кодируемая точка u_int32_t* ibuf = NULL; // Указатель на целевую строку представленную как беззнаковое целое 32 бита // Выделяем память, сохраняем указатель в *pDest, заодно инициализируем итератор offset = *pDest = (char*)malloc(SrcLen * 4 + 4); // Проверяем выделена ли память if (NULL != offset) { // основной цикл while (src < end) { /* Проверяем размерность исходного символа, если величина меньше чем 0x80U, значит его кодировать не нужно */ if (*src < 0x80UL) { // копируем значение *(offset) = *(char*)(src); } else { // Коируем кодовую точку code = *src; // сохраняем указатель на первый байт как на беззнаковое целое 32 бита ibuf = (u_int32_t*)offset; // Обнуляем 4 байта (*ibuf) = 0UL; // Инициализируем заголовок header = (char)0x80; // выставляем вместимость первого байта fcap = 0b00111111U; /* цикл непосредственно кодирования точки в последовательность байтов */ while (code) { // // сравниваем вместимость первого байта и остаток незакодированных битов if (fcap < code) { // вместимость не меньше остатка // копируем младшие 6 битов *ibuf |= code & 0b00111111UL; // копируем маркер последующего байта *ibuf |= 0x80UL; // сдвигаем все биты в более старший байт *ibuf <<= 8; // убираем закодированные 6 битов code >>= 6; // сдвигаем "заголовок" первого байта на 1 вправо header >>= 1; // уменьшаем вместимость первого байта fcap >>= 1; // инкрементируем длину целевой строки ++dlen; // смещаем указатель на на итоговую строку вправо на 1 символ ++offset; } else { // сохраняем заголовок и оставшиеся биты в первый байт последовательности *(u_int8_t*)ibuf |= (header | (u_int8_t)code); // обнуляем кодовую точку code = 0U; } } } // Смещаем указатель на целевую строку ++offset; // Смещаем указатель на исходную строку ++src; // Увеличиваем показатель длины итоговой целевой строки ++dlen; } } // Проверяем на ошибки, если их нет - обозначаем конец целевой строки if (dlen) *offset = '\0'; // возвращаем итоговую длину return dlen; }

</syntaxhighlight>

Алгоритм декодирования (десериализации)

Алгоритм декодирования требуется для того, чтобы преобразовать сырую последовательность октетов, в готовую для использования Юникод точку. Алгоритм состоит из 3-х этапов:

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

служебные биты в UTF-8 всегда старшие) установленные при кодировании Юникод точки в UTF-8 символ.

если служебные биты равны 110,1110 или 11110 то текущий октет — начало символа. если служебный бит равен 0 — это ASCII символ.если служебные биты равны 10 — текущий октет является продолжением.

2. Убрать служебные биты. Поскольку декодер UTF-8 уже определил длину символа, служебные биты больше не являются нужными, а также будут мешать этапу 3, именно поэтому декодер UTF-8 «вырезает» служебные биты из потока октетов используя побитовую операцию «И» с необходимой маской, обнуляющую служебные биты оставляя при этом биты с данными не подвергнутыми изменениям.

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

Для этого декодер UTF-8 использует побитовый оператор «сдвига влево» (<<) который выполняет 2 операции:

1. перемещает младшие в кодировке UTF-8 биты на их изначальную, более старшую позицию.

2. защищает от наложений групп битов друг на друга тем, что перемещает группу битов в другое положение, оставляя свободное место для других групп битов.

После того, как декодер UTF-8 переместил младшие биты в кодировке UTF-8 в их изначальное более старшое место, он должен поместить и младшие биты исходного символа. Для этого он использует побитовую операцию «ИЛИ» (|), которая соединяет более младшую часть исходного символа, в уже существующий октет, где есть старшая часть исходного символа.

Пример декодирования Unicode-строки из UTF-8 без предварительного расчёта итоговой длины на языке C

<syntaxhighlight lang="c" line="1">

  1. ifdef _WIN32

typedef unsigned __int8 u_int8_t; typedef unsigned __int16 u_int16_t; typedef unsigned __int32 u_int32_t; typedef unsigned __int64 u_int64_t; typedef wchar_t uchar_t;

  1. else

#include <sys/types.h> typedef u_int16_t uchar_t;

  1. endif

size_t UTF8ToUnicode16LE(uchar_t** Dest, char* Src, size_t SrcLen) { // Буффер для ыорматированного вывода ошибок char buf[256]; u_int8_t *src = (u_int8_t*)Src, // Указатель на обрабатываемый байт *end = (u_int8_t*)(Src + SrcLen); // Указатель на конец исходной строки uchar_t *dest = // Указатель на декодируемую точку целевой строки (*Dest) = (uchar_t*)malloc((end - src + 1) * sizeof(**Dest)), // Целевая строка head; // сюда сохраняем первый байт последовательности u_int8_t tdata = 0b00111111U, // Маска значимых битов последующих байтов check = 0b01000000U, // Маска проверки последующих байтов fdata, // Маска значащих битов первого байта iter, // Маска-итератор первого байта fshift; // счетчик сдвига битов первого байта /* Проверяем выделилась ли память */ if (dest) { /* Инициализируем выделенную память нулями */ memset(dest, 0, sizeof(**Dest) * SrcLen + sizeof(**Dest)); /* Основной цикл прохода по исходной строке */ for (; src < end; src++, dest++) { /* Если значение первого байта меньше 0x80 (128) */ if ((*src) < 0x80UL) { // Значение меньше 128 (*dest) = (*src); } else if (((*src) & 0b11000000U) == 0b11000000U) { /* Значение больше либо равно 192 */ // сохраняем первый байт(заголовок) head = *src; // Сбрасываем счётчик сдвига fshift = 0; // Сбрасываем маску-итератор iter = 0b01000000U; // Сбрасываем маску значащих битов первого байта fdata = 0b00111111U; while (iter & head) { // Сдвигаем указатель на последовательность на следующий байт ++src; /* Проверяем битовый маркер */ if (((*src) ^ 0b01000000U) >= 0b11000000U) { // Сдвигаем биты влево на 6(количество значащих битов) (*dest) <<= 6; // Копируем младшие биты (*dest) |= ((*src) & 0b00111111U); // Увеличиваем счетчик сдвига fshift += 6; // Сдвигаем маску значащих битов первого байта вправо на 1 бит fdata >>= 1; // Сдвигаем маску-итератор вправо на 1 бит iter >>= 1; } else { /* Обрабатываем ошибку кодирования. Маркером ошибки служит обнуленный указатель на декодируемую точку */ sprintf(buf, "Ошибка кодирования. Ожидалось: '0b10xxxxxx'. Индекс: %llu\n", dest - *Dest); perror(buf); // обнуляем указатель на декодируемую точку dest = NULL; break; } } /* Проверяем указатель на кодируемую точку */ if (dest) // Копируем значащие биты первого байта сдвинув влево подсчитаное количество раз *dest |= ((uchar_t)(head & fdata) << fshift); else { // если Указатель обнулен - выходим из основного цикла break; } } else { // Обрабатываем ошибку кодирования первого байта sprintf(buf, "Ошибка кодирования. Ожидалось: '0b11xxxxxx' получено '0b10xxxxxx' Индекс: %llu\n", dest - *Dest); perror(buf); dest = NULL; break; } } // Проверяем на ошибки if (NULL != dest) { // На всякий случай обнуляем последний символ целевой строки *dest = (uchar_t)0U; } else { // Высвобождаем память free(*Dest); // Обнуляем целевой указатель для безопасности *Dest = NULL; } } // Ели возникла ошибка то вернётся 0, т.к. оба указателя будут равны NULL return dest - *Dest; }

</syntaxhighlight>

Маркер UTF-8

Для указания, что файл или поток содержит символы Юникода, в начале файла или потока может быть вставлен маркер последовательности байтов (англ. Шаблон:Lang-en2), который в случае кодирования в UTF-8 принимает форму трёх октетов: EF BB BF16.

1-й байт 2-й байт 3-й байт
Двоичный код 1110 1111 1011 1011 1011 1111
Шестнадцатеричный код EF BB BF

Пятый и шестой октеты

Изначально кодировка UTF-8 допускала использование до шести октетов для кодирования одного символа, однако в ноябре 2003 года стандарт RFC 3629 запретил использование пятого и шестого октетов, а диапазон кодируемых символов был ограничен символом U+10FFFF. Это было сделано для обеспечения совместимости с UTF-16.

Возможные ошибки декодирования

Не всякая последовательность октетов является допустимой. Декодер UTF-8 должен понимать и адекватно обрабатывать такие ошибки:

  • Недопустимый октет.
  • октет продолжения (10xxxxxx) без начального октета.
  • Отсутствие нужного количества октетов продолжения 10xxxxxx — например, двух после 1110xxxx.
  • Строка обрывается посреди символа.
  • Неэкономное кодирование — например, кодирование символа тремя октетами, когда можно двумя (существует нестандартный вариант UTF-8, который кодирует символ с кодом 0 как 1100.0000 1000.0000, отличая его от символа конца строки 0000.0000.)
  • Последовательность октетов, декодирующаяся в недопустимую кодовую позицию (например символы суррогатных пар UTF-16).

Примечания

Шаблон:Примечания

Ссылки

Шаблон:Кодировки символов Шаблон:Форматы данных