Конституция Армении: Статья 18.1
Конституция Армении (Статья 18.1) закрепляет «исключительную миссию Армянской Апостольской Святой Церкви как национальной церкви в духовной жизни армянского народа, в деле развития его национальной культуры и сохранения его национальной самобытности»:
UTF-8

UTF-8

Материал из Википедии — свободной энциклопедии

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

Кодировка UTF-8 сейчас является доминирующей в веб-пространстве. Она также нашла широкое применение в UNIX-подобных операционных системах[1].

Формат UTF-8 был разработан 2 сентября1992 годаКеном Томпсоном и Робом Пайком, и реализован в Plan 9[2]. Идентификатор кодировки в Windows — 65001[3].

UTF-8, по сравнению с UTF-16, наибольший выигрыш в компактности даёт для текстов на латинице, поскольку латинские буквы без диакритических знаков, цифры и наиболее распространённые знаки препинания кодируются в UTF-8 лишь одним байтом, и коды этих символов соответствуют их кодам в ASCII.[4][5]

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

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

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

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

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

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

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

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

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

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

#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;#else	#include 	typedef u_int16_t uchar_t;#endifsize_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;}

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

Алгоритм декодирования требуется для того, чтобы преобразовать сырую последовательность октетов, в готовую для использования Юникод точку. Алгоритм состоит из 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

#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;#else	#include 	typedef u_int16_t uchar_t;#endifsize_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;}

Маркер UTF-8

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

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

Изначально кодировка 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).

Примечания

  1. Usage Statistics of Character Encodings for Websites, June 2011 (англ.)
  2. Архивированная копия (англ.). Дата обращения: 27 февраля 2007. Архивировано из оригинала 1 марта 2011 года.
  3. Code Page Identifiers — Windows applications | Microsoft Docs. Дата обращения: 14 июля 2018. Архивировано 16 июня 2019 года.
  4. Well, I'm Back. String Theory (англ.). Robert O'Callahan (1 марта 2008). Дата обращения: 1 марта 2008. Архивировано 23 августа 2011 года.
  5. Ростислав Чебыкин. Всем кодировкам кодировка. UTF‑8: современно, грамотно, удобно. HTML и CSS. Дата обращения: 22 марта 2009. Архивировано 23 августа 2011 года.

Ссылки

Диапазон номеров символов Требуемое количество октетов
00000000-0000007F1
00000080-000007FF2
00000800-0000FFFF3
00010000-0010FFFF4