{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "Блоги: заметки с тегом utf8",
    "_rss_description": "Автоматически собираемая лента заметок, написанных в блогах на Эгее",
    "_rss_language": "ru",
    "_itunes_email": "",
    "_itunes_categories_xml": "",
    "_itunes_image": false,
    "_itunes_explicit": "no",
    "home_page_url": "https:\/\/blogengine.ru\/blogs\/tags\/utf8\/",
    "feed_url": "https:\/\/blogengine.ru\/blogs\/tags\/utf8\/json\/",
    "icon": false,
    "authors": [
        {
            "name": "Илья Бирман",
            "url": "https:\/\/blogengine.ru\/blogs\/",
            "avatar": false
        }
    ],
    "items": [
        {
            "id": "125662",
            "url": "https:\/\/bolknote.ru\/all\/chut-bolee-bystry-podschyot-dliny-stroki-v-utf-8\/",
            "title": "Чуть более быстрый подсчёт длины строки в UTF-8",
            "content_html": "<p>Сегодня очень плохо спал — всё время просыпался, потом долго ворочался, не мог уснуть. Утром оказалось мозг никак не мог успокоиться после <a href=\"https:\/\/bolknote.ru\/all\/razbor-bystrogo-podschyota-dliny-stroki-v-utf-8\/\">вчерашней заметки<\/a> про разбор быстрого алгоритма для подсчёта длины строки в <i>UTF-8<\/i> при помощи векторизации для процессоров <i>ARM<\/i>.<\/p>\n<p>Вчера я остановился на том, что у разбираемого способа есть две очевидные недоработки — ненужное вычисление, которое решается инвертированием условия и подсчёт «хвоста», который всегда вычисляется «классическим» способом — без векторизации, хотя если строка у нас кратна вектору, можно было бы этого избежать.<\/p>\n<p>Первый недостаток исправлялся очень просто, а хорошая реализация второй проверки мне вчера в голову не пришла.<\/p>\n<p>Зато, когда я проснулся, она уже была у меня в голове. Всего три операции — первой получаем вектор, где на позициях с ненулевым байтом записывается 255, потом делается операция «И» с номерами позиций и весь вектор суммируется.<\/p>\n<p>В итоге, если ноль стоит в векторе только на крайней правой позиции, у нас получается сумма арифметической прогрессии без последнего элемента.<\/p>\n<p>По замерам такой вариант чуть быстрее.<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">size_t strlen_utf8(unsigned char * p) {\n    size_t len = 0;\n\n    const int8x16_t threshold = vdupq_n_s8(-63);\n    const uint8x16_t delta = vdupq_n_u8(1);\n\n    for(;;) {\n        int8x16_t operand = vld1q_s8((const int8_t * ) p);\n        if (vminvq_u8(operand) == 0) {\n            uint8x16_t pos = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};\n            uint8_t sum = vaddvq_u8(vandq_u8(vtstq_u8(operand, operand), pos));\n\n            \/\/ арифметическая прогрессия без последнего члена\n            if (sum == 16*(16+1) \/ 2 - 16) {\n                uint8x16_t gt = vcgtq_s8(operand, threshold);\n                return len + vaddvq_u8(vandq_u8(gt, delta)) - 1;\n            } else {\n                break;\n            }\n        }\n\n        uint8x16_t gt = vcgtq_s8(operand, threshold);\n        len += vaddvq_u8(vandq_u8(gt, delta));\n        p += sizeof(uint8x16_t);\n    }\n\n    for (signed char c; (c = *p); p++) {\n        if (c &gt;= -64) {\n            len++;\n        }\n    }\n\n    return len;\n}<\/code><\/pre>",
            "date_published": "2024-01-28T10:42:29+05:00",
            "date_modified": "2024-02-05T23:39:57+05:00",
            "tags": [
                "utf8",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Sun, 28 Jan 2024 10:42:29 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125662",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "125661",
            "url": "https:\/\/bolknote.ru\/all\/razbor-bystrogo-podschyota-dliny-stroki-v-utf-8\/",
            "title": "Разбор быстрого подсчёта длины строки в UTF-8",
            "content_html": "<p>Давайте попробуем всё-таки разобраться как работает <a href=\"https:\/\/bolknote.ru\/all\/utf-8-na-arm\/\">быстрое вычисление<\/a> длины строки в кодировке <i>UTF-8<\/i>. В <a href=\"https:\/\/bolknote.ru\/all\/opredelyaem-granicu-simvola-v-utf-8\/\">прошлый раз<\/a> мы узнали, что для этого нам надо подсчитать все байты, которые будут больше или равны −64.<\/p>\n<p>Напомню как выглядел код:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">size_t strlen_php(unsigned char * p) {\n    size_t len = 0;\n\n    const int8x16_t threshold = vdupq_n_s8(-64);\n    const uint8x16_t delta = vdupq_n_u8(1);\n\n    for(;;) {\n        int8x16_t operand = vld1q_s8((const int8_t * ) p);\n        if (vminvq_u8(operand) == 0) {\n            break;\n        }\n\n        uint8x16_t lt = vcltq_s8(operand, threshold);\n        len += sizeof(uint8x16_t) - vaddvq_u8(vandq_u8(lt, delta));\n        p += sizeof(uint8x16_t);\n    }\n\n    for (signed char c; (c = *p); p++) {\n        if (c &gt;= -64) {\n            len++;\n        }\n    }\n\n    return len;\n}<\/code><\/pre><p>Для ускорения подсчёта в алгоритме используется векторизация, — это когда мы работаем с пачкой чисел, делая над каждым из них какое-то действие за одну операцию. В данном случае используется вектор 8×16 — шестнадцать значений по восемь бит (типы <tt>uint8x16_t<\/tt> и <tt>int8x16_t<\/tt>).<\/p>\n<p>Вектор загружается функцией <tt>vld1q_s8<\/tt>, где коды символов считываются по адресу, записанному в указателе <tt>p<\/tt>. В дальнейшем мы будем его сдвигать на размер вектора, чтобы забирать следующую пачку данных.<\/p>\n<p>Ниже используется функция <tt>vminvq_u8<\/tt>, которая вычисляет минимальное значение в векторе, рассматривая его как набор байтов без знака. Если там ноль, значит строка кончилась и в нашем векторе — хвост, который надо досчитать обычным способом, без вектора. Тут можно было бы проверить на какой позиции слева находится признак конца строки, если на самой последней, то строка была кратна вектору. <s>Но я не стал заморачиваться.<\/s> Но такая проверка оказалась медленнее подсчёта длины хвоста обычным способом.<\/p>\n<p>Дальше функция <tt>vcltq_s8<\/tt> сравнивает каждый байт в векторе со значением −64  — это значение загружается в каждый элемент вектора для сравнения функцией <tt> vdupq_n_s8<\/tt>.<\/p>\n<p>Результирующий вектор заполняется значениями 0 или 255, в зависимости от результата сравнения. Функция <tt>vandq_u8<\/tt> производит операцию «И» с единицей, после чего у нас в векторе ноль остаётся неизменным, а 255 превращается в единицу. Эти единицы мы суммируем функцией <tt>vaddvq_u8<\/tt>.<\/p>\n<p>Теперь у нас получился вектор, в котором подсчитаны все внутренние байты символов, а чтобы подсчитать внешние, надо вычесть это число из длины вектора. Правильнее было бы инвертировать сравнение, до этого в разгаре экспериментов просто не дошли руки.<\/p>\n<p>Полученное число мы добавляем к вычисляемой длине и сдвигаем указатель. В конце досчитываем хвост обычным циклом. Конец алгоритма.<\/p>\n",
            "date_published": "2024-01-28T00:51:57+05:00",
            "date_modified": "2024-01-28T01:47:41+05:00",
            "tags": [
                "utf8",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Sun, 28 Jan 2024 00:51:57 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125661",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "125624",
            "url": "https:\/\/bolknote.ru\/all\/opredelyaem-granicu-simvola-v-utf-8\/",
            "title": "Определяем границу символа в UTF-8",
            "content_html": "<p>В Телеграме попросили рассказать как работает код, который я <a href=\"https:\/\/bolknote.ru\/all\/utf-8-na-arm\/\">приводил вчера<\/a> в заметке про ускорение функции, возвращающей количество символов в строке с кодировкой <i>UTF-8<\/i>.<\/p>\n<p>Там действительно есть что рассказать, но рассказ придётся разбить на несколько частей. Я постараюсь по возможности не погружаться в дебри. В этой части начну с того, что расскажу чем вообще необычна эта кодировка.<\/p>\n<p>Все кодировки условно можно разделить на две части: кодирующие символ фиксированным или переменным количеством байт. К первым можно отнести, например, <i>CP1251<\/i>, когда-то широко используемую в ОС «Виндоуз» — там символ кодировался одним байтом, ко вторым — <i>UTF-8<\/i>, где один символ может занимать от одного до четырёх байт.<\/p>\n<p>Справедливости ради, есть и <a href=\"https:\/\/bolknote.ru\/all\/3283\/\">другие виды кодирования<\/a>, но нам их рассматривать ни к чему.<\/p>\n<p>Тут важно то, что для строки в <i>UTF-8<\/i>, нельзя получить её длину, подсчитав количество байт и разделив их на коэффициент хранения. Для каждого байта нам надо понять что он кодирует и считать только те байты, которые отвечают за начало символа. Как это сделать?<\/p>\n<p>Посмотрим на картинку из «<a href=\"https:\/\/ru.wikipedia.org\/wiki\/UTF-8\">Википедии<\/a>», которая показывает как устроена эта кодировка:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2024.01.25@2x.png\" width=\"727\" height=\"180\" alt=\"\" \/>\n<\/div>\n<p>Тут нам интересна только колонка «шаблон», где видно, что все «внутренние» байты символов начинаются с одной и той же сигнатуры — старший бит установлен, младшие — сброшены.<\/p>\n<p>То есть, байты в диапазоне от <tt>10000000<\/tt> до <tt>10111111<\/tt> (в бинарном представлении) — внутренние байты, остальные — начало символа. Опять же, согласно таблице начала попадают в диапазоны <tt>00000000<\/tt>-<tt>01111111<\/tt> и от <tt>11000000<\/tt> до <tt>11011111<\/tt>.<\/p>\n<p>Умные люди заметили одну закономерность, сейчас мы её тоже увидим. Давайте напишем небольшую программу:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">#include &lt;stdio.h&gt;\n\nint main() {\n    const signed char arr[] = {\n        0b10000000, 0b10111111, 0b00000000, 0b01111111, 0b11000000, 0b11011111\n    };\n\n    for (int i = 0; i &lt; sizeof(arr) \/ sizeof(arr[0]); i++) {\n        for (int b = 128; b; b &gt;&gt;= 1) {\n            printf(&quot;%d&quot;, (arr[b] &amp; i) &gt; 0);\n        }\n\n        printf(&quot; % d\\n&quot;, arr[i]);\n    }\n}<\/code><\/pre><p>Вот что она выводит (диапазон, в котором находятся внутренние байты, я выделил другим цветом):<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2024.01.25.1@2x.png\" width=\"727\" height=\"159\" alt=\"\" \/>\n<\/div>\n<p>Закономерность в том, что все внутренние байты в знаковом представлении по значению меньше −64. Это довольно дешёвая проверка с точки зрения машинного кода, что очень хорошо, так как наша цель — производительность.<\/p>\n<p>Про остальное — в следующий раз.<\/p>\n",
            "date_published": "2024-01-25T14:04:08+05:00",
            "date_modified": "2024-01-28T00:07:56+05:00",
            "tags": [
                "utf8",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Thu, 25 Jan 2024 14:04:08 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125624",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "125614",
            "url": "https:\/\/bolknote.ru\/all\/utf-8-na-arm\/",
            "title": "UTF-8 на ARM",
            "content_html": "<p>Пока проект внедрения Юникода во «Флиппер Зеро» на паузе, — разработчики занимаются обновлением одной из важных библиотек, я вспомнил, что вообще-то в природе существуют разные способы ускорения обработки строки в кодировке <i>UTF-8<\/i>. Именно её я выбрал для хранения строк. А поскольку железо у «Флиппера» весьма ограниченное, может быть имеет смысл подумать как сэкономить ресурсы.<\/p>\n<p>Например, когда-то, ещё во времена работы в «Яндексе», я <a href=\"https:\/\/bolknote.ru\/all\/2855\/\">сравнивал<\/a> между собой различные реализации функции <tt>strlen<\/tt>, где ускорение достигалось за счёт векторизации.<\/p>\n<p>Любопытно посмотреть как обстоят дела с ускорением таких вещей на процессоре, который установлен на «Флиппере». Правда до него у меня руки ещё не дошли, но я уже потренировался на своём «Макбуке», благо тут процессор той же архитектуры.<\/p>\n<p>Идея всё та же — векторизация. За референс я взял наивную реализацию с перебором по одному байту:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">size_t strlen_naive(const char* str) {\n    size_t len = 0;\n    char ch;\n\t\n    while ((ch = *str++)) {\n        len += (ch &amp; 0b11000000) != 0b10000000;\n    }\n\n    return len;\n}<\/code><\/pre><p>Попробовал несколько вариантов, но лучше всех показали себя две функции: та, которую я упоминал в первом абзаце и переделанная нашими общими с братишкой усилиями <a href=\"https:\/\/github.com\/php\/php-src\/blob\/ea3c541640a75340ea06c460cf282bde5bf75b13\/ext\/mbstring\/mbstring.c#L1772\">реализация<\/a>, найденная в недрах <i>PHP<\/i>.<\/p>\n<p>После переделки она стала выглядеть так:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">size_t strlen_php(unsigned char * p) {\n    size_t len = 0;\n\n    const int8x16_t threshold = vdupq_n_s8(-64);\n    const uint8x16_t delta = vdupq_n_u8(1);\n\n    for(;;) {\n        int8x16_t operand = vld1q_s8((const int8_t * ) p);\n        if (vminvq_u8(operand) == 0) {\n            break;\n        }\n\n        uint8x16_t lt = vcltq_s8(operand, threshold);\n        len += sizeof(uint8x16_t) - vaddvq_u8(vandq_u8(lt, delta));\n        p += sizeof(uint8x16_t);\n    }\n\n    for (signed char c; (c = *p); p++) {\n        if (c &gt;= -64) {\n            len++;\n        }\n    }\n\n    return len;\n}<\/code><\/pre><p>Повторюсь, у меня пока не дошли руки попробовать это всё на «Флиппере», но на моём «Макбуке» результаты выглядят следующим образом. Это десять тысяч прогонов на русскоязычном тексте, размер которого чуть больше ста тысяч символов.<\/p>\n<pre class=\"e2-text-code\"><code class=\"plaintext\">Name   Length  Time\n----   ------  ----\nPHP    117465   97797\nFast   117465  143780\nNaïve  117465  531535<\/code><\/pre><p>Не скажу, что я делал всё как положено, — надо бы считать среднее и отклонение, разносить по разным файлам, научиться прибивать процесс к производительным ядрам, но разница в результатах достаточно показательна (я запускал несколько десятков раз), поэтому, как мне кажется, и так нормально.<\/p>\n<p>Цифры в попугаях (больше — хуже), которые выдаёт сишная функция <tt>clock()<\/tt>, компилировал компилятором <i>Clang 15<\/i> с ключом <tt>-O3<\/tt>.<\/p>\n",
            "date_published": "2024-01-24T22:13:28+05:00",
            "date_modified": "2024-01-28T00:41:04+05:00",
            "tags": [
                "flipper zero",
                "utf8",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Wed, 24 Jan 2024 22:13:28 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125614",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "118787",
            "url": "https:\/\/bolknote.ru\/all\/perekodirovka-vruchnuyu\/",
            "title": "Перекодировка вручную",
            "content_html": "<p>Меня удивляет, что многие программисты сейчас не умеют перекодировать строку в ошибочной кодировке вручную, даже если знают как она получилась.<\/p>\n<p>С различными кодировками сейчас мало где можно столкнуться, тем не менее мне неясно в чём сложность задачи. Вроде даже ничего знать не надо.<\/p>\n<p>Например, я вижу вот такую строку: <tt>РўСЂРµР±СѓРµС‚СЃСЏ Р°РІС‚РѕСЂРёР·Р°С†РёСЏ<\/tt>. Она так выглядит, потому что она была в кодировке <i>UTF-8<\/i>, но код подумал, что она с <i>CP1251<\/i> и перекодировал её в <i>UTF-8<\/i>. Предположим это всё известно, как вернуть всё в норму и посмотреть что там написано?<\/p>\n<p>Почему-то мало кто справляется с задачей в такой формулировке. Вроде всё просто — строка перекодировалась из <i>CP1251<\/i> в <i>UTF-8<\/i>, значит надо перекодировать по этой цепочке обратно:<\/p>\n<pre class=\"e2-text-code\"><code class=\"bash\">iconv -f utf8 -t cp1251 &lt;&lt;&lt;&quot;РўСЂРµР±СѓРµС‚СЃСЏ Р°РІС‚РѕСЂРёР·Р°С†РёСЏ&quot;\nТребуется авторизация<\/code><\/pre>",
            "date_published": "2023-04-26T20:41:37+05:00",
            "date_modified": "2024-01-28T00:08:15+05:00",
            "tags": [
                "utf8",
                "программирование"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Wed, 26 Apr 2023 20:41:37 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "118787",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "118020",
            "url": "https:\/\/bolknote.ru\/all\/gopnik-2-utf-8\/",
            "title": "«Гопник-2»: UTF-8",
            "content_html": "<p>Продолжаю писать про портирование «Гопника-2» под «Виндоуз», ибо там ещё есть о чём рассказать.<\/p>\n<p>Я уже очень давно не работаю под этой операционной системой, поэтому некоторые обычные, возможно, для многих вещи для меня являются открытием. Когда я начинал процесс портирования игры под эту платформу, я был уверен, что столкнусь в трудностями совершенно в других частях кода. Одной из неожиданностей была плохая работа «Виндоуз» с кодировкой ЮТФ-8.<\/p>\n<p>Если вывод в консоль <a href=\"https:\/\/bolknote.ru\/all\/gopnik-2-adaptaciya-pod-windows\/\">удалось победить<\/a> сравнительно легко, то со вводом возникли проблемы — вызов <tt>SetConsoleCP(CP_UTF8)<\/tt>, который должен был помочь, не помог. В интернете пишут, что он, по всей видимости, не реализован — просто не делает ничего.<\/p>\n<p>Если с латинскими буквами проблем не возникает, они кодируются одним байтом, это ничем не отличается от того как программы до этого работали десятилетиями, а вот с русскими буквами беда — для них требуется два байта и в таком виде Виндоуз их не понимает.<\/p>\n<p>Рекомендуемый специалистами способ — переключить консоль в режим ввода кодировки ЮТФ-16, а потом перекодировать полученную строку в нужную кодировку. «Виндозу» ЮТФ-16 родная, поэтому проблем вообще никаких не возникает.<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">#ifdef __MINGW32__\n        int wlen = 100;\n        int save = _setmode(_fileno(stdin), _O_U16TEXT);\n        wchar_t *wstr = (wchar_t *) malloc(wlen * sizeof(wchar_t));\n        fgetws(wstr, wlen, stdin);\n\n        int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, 0, 0, 0, 0);\n        user_name = (char *) malloc(len);\n        WideCharToMultiByte(CP_UTF8, 0, wstr, -1, user_name, len, 0, 0);\n        _setmode(_fileno(stdin), save);\n\n        free(wstr);\n#endif<\/code><\/pre><p>Мне, к счастью, ввод русских букв нужен только в одном месте — там где игрок вводит своё имя. Я убрал используемую там функцию <tt>fgets<\/tt> под условную компиляцию и вставил кусок, который выше. С таким исправлением всё заработало, как ожидается.<\/p>\n",
            "date_published": "2023-04-07T13:33:31+05:00",
            "date_modified": "2024-01-28T00:08:38+05:00",
            "tags": [
                "gopnik",
                "utf8",
                "игра «Гопник-2»",
                "программирование",
                "си++"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Fri, 07 Apr 2023 13:33:31 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "118020",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "125660",
            "url": "https:\/\/bolknote.ru\/all\/2092\/",
            "title": "I � Unicode",
            "content_html": "<p>Давайте я вам про Unicode ещё раз расскажу?<\/p>\n<p>Как известно, в памяти компьютера числа представлены битами, которые группируются в байты. Один байт может хранить одно из 256 значений (поскольку состоит из восьми бит, каждый из которых может хранить одно из двух). Следовательно, числа, значения которых &gt; 255 хранятся в больше, чем одном байте.<\/p>\n<p>Например, числа до 65535 можно уместить уже в двух байтах: в так называемом старшем записывается сколько раз полных 256 содержится в числе, а в младшем — остаток: старший × 256 + младший.<\/p>\n<p>В зависимости от типа процессора, порядок, в котором записаны в памяти старшие и младшие байты, различается. Собственно, мне хорошо известны только две системы: младший записывается первым (как в процессорах Intel) и старший записывается первым (в процессорах ARM, которые стоят в смартфонах). Есть ещё смешанная, но с ней я не сталкивался. Системы эти носят имена: little-endian и big-endian (системы со смешанным порядком называются middle-endian и термин не указывает на то как именно «мешается» этот порядок). Краткая запись названий — LE и BE.<\/p>\n<p>Есть ещё системы, которые умеют переключать порядок (те же ARM) и называются bi-endian.<\/p>\n<p>Термины little-endian и big-endian пришли к нам из «Приключений Гулливера» и на русский переводятся как «тупоконечный» и «остроконечный». Те, кто читали, те помнят (война по поводу того с какой стороны разбивать яйца). Информатика тут какбэ намекает. Хотя у каждой системы есть свои достоинства и (не удержался) мне ближе LE.<\/p>\n<p>Сюрприз для непрограммистов: буквы в памяти компьютера тоже представлены числом. Это просто номер по порядку в компьютерном алфавите. Так девочки в нашем классе «кодировали» записки: вместо букв ставили номер позиции в алфавите. В чём-то они были правы, но только не в том, что это шифр.<\/p>\n<p>Когда-то компьютеры победивших сейчас систем использовали всего 256 символов и всем было хорошо — туда умещались все символы, которые присутствовали в том мире, где эти компьютеры создавались. Экспансия привела к тому, что 256 значений для символов перестало хватать.<\/p>\n<p>Было принято очевидное решение — выделять на символ не один байт, а несколько. Так появился стандарт Unicode, где огромному количеству символов дано своё число и закреплены начертания, стандарт дополняется и новые версии выходят почти каждый год.<\/p>\n<p>Система кодирования Unicode, где выделяются два байта, называется UTF-16 (16 бит на символ), там где четыре байта — UTF-32 (32 бита). Название UCS-4 (четыре байта) является синонимом UTF-32, а UCS-2 (два байта) подмножеством UTF-16. UCS-2 <a href=\"http:\/\/www.unicode.org\/faq\/basic_q.html#14\">отличается от UTF-16<\/a> отсутствием так называемых «суррогатных пар» (которые появились только в Unicode 2.0, вы не хотите знать что это) и является устаревшим стандартом, можете про него забыть.<\/p>\n<p>Так как способов хранения чисел, не умещающихся в памяти у нас несколько, то системы подразделяются на UTF-16BE, UTF-16LE, UTF-32BE и UTF-32LE. Отсюда видно, что UCS-4LE это тоже, что и UTF-32LE. Если порядок байт не указан, то <a href=\"http:\/\/unicode.org\/faq\/utf_bom.html#gen7\">принято считать<\/a>, что используется big-endian.<\/p>\n<p>Первого апреля 2005-го года были предложены шуточные «стандарты» <a href=\"http:\/\/tools.ietf.org\/html\/rfc4042\">UTF-9 и UTF-18<\/a>, отношения к рассматриваемой проблеме они имеют. Для телеграфа и прочего слоновьего гуано, разрабатывались UTF-5 и UTF-6, но о их судьбе мне ничего не известно. Так же есть <a href=\"http:\/\/en.wikipedia.org\/wiki\/UTF-7\">UTF-7<\/a>, который в стандарт не вошёл, но реально применяется (в модифицированном виде) внутри почтового протокола <a href=\"http:\/\/tools.ietf.org\/html\/rfc3501\">IMAP4<\/a>, про него я рассказывать не буду, мне он стал известен из-за <a href=\"http:\/\/old.antichat.ru\/txt\/utf7\/\">оригинального способа его использования<\/a> для XSS-атак в IE (в частности, решением этой проблемы я занимался в PEAR PHP <a href=\"http:\/\/pear.php.net\/package\/HTML_Safe\">классе HTML_Safe<\/a>). Можно упомянуть ещё <a href=\"http:\/\/en.wikipedia.org\/wiki\/UTF-1\">UTF-1<\/a>, но с ней я не сталкивался в работе.<\/p>\n<p>BOM. BOM расшифровывается как «byte order mark» (признак порядка байт) и ставится внутри файлов упомянутых двух- и четырёхбайтных кодировок. <a href=\"http:\/\/unicode.org\/faq\/utf_bom.html#gen6\">Если BOM внутри файла не встретился, принимается порядок big-endian<\/a>. У BOM есть значание. В UCS-2 это 65279 (для программистов — FEFF), для UCS-4\/UTF-32 — это 4278124544 (FEFF0000). Число выбрано так, чтобы старшие и младшие байты у них не совпадали и по их порядку можно было бы определить какой порядок байт используется. К сожалению, BOM не даёт возможности определить использутеся двух- или четырёхбайтная кодировка.<\/p>\n<p>Теперь непрограммистам будет трудно.<\/p>\n<p>Пока всё было достаточно просто, но человечество придумало ещё одну кодировку — UTF-8, с плавающим размером. Хорошие новости заключаются в том, что порядок следования байт тут определён и никаких LE и BE рядом с UTF-8 не ставится. Соотвественно и BOM тут не нужен. Он может использоваться только для того, чтобы указать программе, что это именно UTF-8 и <a href=\"http:\/\/unicode.org\/faq\/utf_bom.html#BOM\">имеет номер 15711167 (EF BB BF)<\/a>. Откуда можно сделать вывод (дорогие писатели редакторов), что использование в UTF-8 BOM от UTF-16 — ошибка.<\/p>\n<p>Трудность в том, что UTF-8, по сути это ещё один способ записи многобайтовых чисел (а каждая буква в стандарте Unicode — многобайтовое число). У системы есть целых два плюса (ирония!): старая однобайтовая кодировка совместима с UTF-8, а значит буржуинам не нужно переделывать свои программы, если они не используют в них буквы и других языков (например, на любом старом англоязычном сайте как бы уже используется кодировка UTF-8), второй плюс — латиница записывается компактнее (в один символ). Минусы — чисто программисткие: работа с кодировкой требует больше ресурсов из-за плавающего размера.<\/p>\n<p>Итак. Каждый символ в кодировке занимает от 1 до 4-х байт. Вообще, формат устроен так, что можно было бы взять и более длинные цепочки, но в Unicode нет столько символов, чтобы записывать их более длинными последовательностями.<\/p>\n<p>Тут надо вспомнить что такое биты. Бит — единица информации, мельче не бывает, у него всего два значения — 0 или 1. Байт состоит из 8 битов, биты очень удобно записывать в позиционной двоичной системе: 00001011. «Позиционная» тут означает, что значение числа зависит от его позиции. Кстати, это привычная нам система. В числе «22» две двойки, но у первой значение в <i>десять<\/i> раз больше, чем у второй. Это <i>десятичная<\/i> позиционная система. В двоичной, каждая более левая однёрка будет больше в два раза своей соседки.<\/p>\n<p>Таким образом число 1011 расшифровывается из двоичной как 1 × 2<sup>3<\/sup> + 0 × 2<sup>2<\/sup> + 1 × 2<sup>1<\/sup> + 1 × 2<sup>0<\/sup> = 1 × 8 + 1 × 2 + 1 × 1 = 8 + 2 + 1 = 11 в десятичной системе.<\/p>\n<p>UTF-8 устроен следующим образом. Пусть, мы двигаемся по строке, содержащей два байта: 208 и 159. В битах это 11010000 и 10011111. (Немного осталось, потерпите).<\/p>\n<p>В первом символе нужно посчитать количество бит со значением «1» до первого нуля. Это общее количество байт, которым записан данный символ. Если количество байт — один (это вроде как специальный признак), то вы нашли не первый байт символа.<\/p>\n<p>У нас в примере количество бит до первого нуля — два. Значит, буква записана двумя символами — первый это тот, на которым мы находимся и второй — который следует за ним. Каждый байт в UTF-8 разбит на две части — до первого нулевого бита. Первая часть — общая длина байт последовательности, а оставшаяся — значение. Биты из значения записывают последовательно (у нас это 10000 011111) и смотрят какое число получилось (у нас это — 1055, это номер <a href=\"http:\/\/www.fileformat.info\/info\/unicode\/char\/041f\/index.htm\">буквы «П»<\/a> в Unicode).<\/p>\n<p>Могу рассказать про UTF-7 и UTF-1, если интересно. Или про суррогатные пары.<\/p>\n",
            "date_published": "2009-04-15T13:26:00+05:00",
            "date_modified": "2024-01-28T00:08:50+05:00",
            "tags": [
                "utf8",
                "юникод"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Wed, 15 Apr 2009 13:26:00 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125660",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        }
    ],
    "_e2_version": 4079,
    "_e2_ua_string": "Aegea 11.0 (v4079e)"
}