Люди говорят на бесчисленном количестве разных языков. Эти языки не только несовместимы между собой, но и представляют огромную трудность при транспиляции в среде исполнения. К сожалению, все попытки стандартизации провалились.
По крайней мере, в таком положении вещей есть, кого винить: Бога. Ведь именно он вынудил человечество говорить на разных языках из-за древнего спора о строительстве объекта недвижимости.
Однако человечество может винить себя за то, что сложности в общении испытывают компьютеры.
И одна из самых больших проблем одновременно является самой простой: компьютеры не договорились о том, как записывать буквы двоичным кодом.
Как записываются буквы двоичным кодом
Возьмём для примера символ латиницы «A». В American Standard Code for Information Interchange, или ASCII, ему назначено число 65. Такая нумерация была унаследована Unicode, только в Unicode число 65 записывается в шестнадцатеричном виде (
U+0041
). Такую запись называют «элементом кодового пространства» (codepoint).Здесь всё довольно просто; по крайней мере, в вопросе числа, обозначающего «A», в целом есть консенсус. Но компьютеры не могут просто хранить десятичные числа, они хранят только двоичные.
В самой популярной кодировке символов UTF-8 номер символа 65 («A») записывается так:
01000001
Равны единице, или «включены» только второй и последний биты. Второй бит обозначает 64, а последний — 1. В сумме они дают 65. Всё очень просто.
Ещё одна популярная кодировка — это UTF-16, в основном применяемая в мире Windows, Java и
JavaScript. В UTF-16 число 65 записывается следующим образом:
01000001 00000000
Практически то же самое, только UTF-16 использует под каждый символ два полных байта (как минимум), но не требует дополнительных битов для описания 65, так что второй байт остаётся пустым.
А что там с другими кодировками? Вот лишь некоторые из наиболее популярных:
- Win-1252 — не относящаяся к Unicode кодировка, применяется там, где говорят на европейских языках
- KOI8 — не относящаяся к Unicode кодировка, используется там, где применяется кириллица
- GB18030 — Unicode, но в основном применяется в континентальном Китае
- Big5 — не относится к Unicode, широко используется там, где применяются традиционные китайские иероглифы
- Shift_JIS — не относится к Unicode, используется в Японии
Все эти кодировки наследуют от букв ASCII, поэтому во всех них
A
записывается так:01000001
Точно так же, как в UTF-8.
Очень удобно. Именно поэтому базовый западноевропейский алфавит читаем, даже когда остальная часть документа превращается в исковерканный хаос. Многие популярные кодировки (за исключением UTF-16) соответствуют ASCII, по крайней мере, для латиницы.
Пока всё неплохо. Но давайте теперь рассмотрим более сложный символ: знак евро, €. Консорциум Unicode обозначил его числом 8364 (
U+20AC
).В UTF-8 число 8364 представляется в следующем виде:
11100010 10000010 10101100
Обратите внимание, что в UTF-8 оно занимает три байта. UTF-8 — это кодировка символов с «переменной длиной»: чем больше число Unicode, тем больше байтов требуется. (На самом деле это справедливо и для UTF-16, но встречается реже.)
Однако в UTF-16 число 8364 кодируется совершенно иначе:
10101100 00100000
Win-1252 не следует стандарту Unicode. В этой кодировке знак евро имеет номер 128. И кодировка записывает 128 вот так:
10000000
То есть лишь один включённый бит равен 128.
И вот здесь начинаются проблемы. Как только мы покидаем спокойные улочки английского алфавита, кодировки быстро становятся хаотичными.
€
невозможно никак представить в KOI8.В GB18030 символ € кодируется так:
10100010 11100011
В Big5 символ € выглядит так:
10100011 11100001
В Shift JIS это
10000000 00111111
Абсолютно разные и совершенно несовместимые. Если автоматически предполагать, что используется UTF-8, то мы получим полную чушь.
Как определить, какая кодировка используется?
Некоторые форматы сами задают кодировку, например, JSON обязует применять UTF-8. Это сильно упрощает жизнь — если ты знаешь, что данные записаны в JSON, то они должны быть закодированы в UTF-8.
В других случаях можно передать кодировку отдельно. HTTP позволяет помещать кодировку в заголовок
Content-Type
:Content-type: text/html; charset=ISO-8859-1
А у некоторых форматов есть внутренние способы указания кодировки. Например, у некоторых текстовых файлов есть заголовок:
# -*- encoding: utf-16be -*-
Однако это немного сбивает с толку, потому что для того, чтобы найти этот заголовок, нам сначала нужно как-то заранее прочитать файл.
Ну а если у данных нет никаких меток?
Или если метка ошибочна? Как будет показано ниже, встречается такое довольно часто.
В частности, у файлов CSV нет внутреннего способа информирования о том, какая кодировка в них использована. В них нельзя поместить комментарий, потому что для этого нет места, и программы чтения csv чаще всего не смогут распарсить ваш csv. А многие популярные инструменты, работающие с файлами CSV (MS Excel), не используют UTF-8.
Что будет тогда?
Решением будет статистика.
Определение кодировки при помощи статистики
Существует две базовые стратегии для определения кодировки неразмеченной строки текста.
- На уровне байтов
- На уровне символов
В большинстве реализаций сначала используется уровень байтов, а при необходимости уровень символов.
▍ Эвристики уровня байтов
На уровне байтов всё довольно просто. Достаточно взглянуть на байты и понять, выглядят ли они похожими на конкретную кодировку символов.
Например, как я говорил выше, в UTF-16 используется по два байта на символ (чаще всего). В случае текста на латинице (например, английского) обычно появляется множество «пустых» вторых байтов. К счастью, во многих языках разметки активно используется латиница (например,
<
, >
, [
, ]
и так далее), даже если сам документ составлен не на латинице. Если строка текста содержит множество пустых вторых байтов, то есть вероятность, что это UTF-16.Существуют и другие признаки. Представьте, что вы веб-браузер, и ссылка перенесла пользователя к файлу, два первых байта которого имеют следующий вид:
00111100 00100001
Если бы это был UTF-16, то эти два байта оказались бы символом ℼ, имеющим название double struck small pi и номер 8508 (U+213C) в Unicode. Часто ли этот символ первым встречается в файле HTML?
Или более вероятно, что это двухсимвольная последовательность
<!
в кодировке UTF-8? Возможно, следующие байты — это doctype>
или стандартный бойлерплейт любого документа HTML?Ещё одна подсказка — это конкретные байты. Как ни ужасно, UTF-16 имеет две версии: в одной биты записываются обычным образом, в другой — в обратную сторону. Чтобы люди различали эти две версии, в стандарте UTF-16 есть маркер последовательности байтов (byte order mark), который можно поместить перед текстовым потоком, чтобы обозначить используемую версию. Эта пара байтов редко встречается в других кодировках, и практически никогда не бывает в начале, так что они становятся хорошей подсказкой о том, что идёт за ними.
Итак, байты могут дать нам довольно много информации о кодировке. Если вы можете с их помощью однозначно определить UTF-8 или UTF-16, то наша задача выполнена.
▍ Эвристики уровня символов
Сложности возникают с однобайтовыми кодировками, не относящимися к Unicode. Например, сложно отличить Win-1252 от KOI8, ведь для кодирования разных вещей и та, и другая используют обычно пустой первый бит ASCII.
Как же их отличить? Благодаря частотному анализу. Мы смотрим на буквы, которые могли бы присутствовать в документе, например, если это KOI8, и задаёмся вопросом: «Действительно ли это типичное распределение букв для документа на кириллице?».
Вот базовый алгоритм:
- Исключаем все кодировки, отсечённые предыдущими эвристиками уровня байтов
- Для каждой оставшейся возможной кодировки
X
:- Парсим входные данные, как будто они были закодированы
X
- Сравниваем частотность символов в строке со значениями в известной таблице частотности
- Опционально также сравниваем пары букв (например,
qu
) с известной таблицей частотности - Если они достаточно хорошо совпадают, то возвращаем
X
- Парсим входные данные, как будто они были закодированы
- В противном случае возвращаем ошибку
Часто таким образом можно также определить, на каком языке написан этот документ — именно благодаря этому веб-браузеры открывают диалоговое окно «Перевести эту страницу?».
Действительно ли это работает?
Обычно люди не очень любят эвристики, но ответом является «да». Это работает, и на удивление хорошо. И намного лучше, чем просто предположения о том, что текст закодирован UTF-8 (в конечном итоге это и является бенчмарком).
Вероятно, нас не должно удивлять, что статистика работает. Она часто хорошо работает с языками, от первых эффективных спам-фильтров до множества других вещей.
Эвристики тоже важны, потому что люди понимают кодировки неправильно.
Может показаться логичным, что если вы экспортируете лист Excel в файл csv в последней версии MS Excel, то получите UTF-8. Ну, или, возможно, UTF-16. Но вы ошибётесь. По умолчанию, в большинстве конфигураций Excel сохраняет CSV в кодировке Win-1252.
Win-1252 — это однобайтная кодировка, не относящаяся к Unicode. Это расширение ASCII, засовывающее в неиспользованный восьмой бит достаточно большое количество символов для почти каждого европейского языка. Обычный пользователь Excel никогда о ней не слышал, если вообще слышал о кодировках символов. Во многой мудрости много печали.
Дополнительные источники
Вероятно, основная часть кода определения кодировок работает на принципах, заложенных Netscape в начале 2000-х. Статья с описанием этого подхода есть в архиве Mozilla.
У меня есть чёткое впечатление, что автоматическое определение кодировки текста — это частный случай закона Постела: «будь консервативным в том, что делаешь, будь либерален в том, что принимаешь от других». Я всегда воспринимал закон Постела как что-то истинное, но сейчас у меня возникает всё больше сомнений. Возможно, механизм автоматического определения кодировки в моей базе данных csvbase стоит сделать частью пользовательского интерфейса, а не заранее выбранным пунктом выпадающего списка.
Telegram-канал со скидками, розыгрышами призов и новостями IT ?
Комментарии (61)
petropavel
02.05.2024 13:31+19Такое впечатление, что автор этого поста, да и переводчик, зашли в какую-то пещеру лет 20 назад и только недавно проснулись. По крайней мере 20 лет назад я в последний раз писал автоопределение кодировок. Да и бнопню видел в последний раз примерно тогда же
DMGarikk
02.05.2024 13:31+5Да и бнопню видел в последний раз примерно тогда же
А это не обязательно видеть, достаточно столкнутся с тем что какойто софт не работает с файлами которые лежат в C:\Пользователи\Документы Васяна\Какойто-Хлам\Самыйнужный файлик 123.docx ( для любителей похейтить винду - /home/vasyan/Документы Васяна/Какойто-Хлам/Самыйнужный файлик 123.docx )
и такого софта внезапно больше чем хотелось бы
vadimr
02.05.2024 13:31+4Обозначенная проблема, скорее всего, связана с пробелом, а не с кодировками.
DMGarikk
02.05.2024 13:31+3а чем вам пробел не символ?
вообще проблема не только с пробелом, софт зачастую не признает ничего кроме ascii символов
например такойже путь с пробелами но на английском будет работать нормально почти везде где разработчики не совсем идиоты (с остальным уже поправку на то можно делать что в мире нет языка кроме английского если вы в штатах живете)
vadimr
02.05.2024 13:31+2Многие программы вызывают скрипты и передают имена файлов в качестве параметров. Если не думать об экранировании, то со всякими пробелами, кавычками и бекслешами возникают вопросы.
В macOS, кстати, можно символ "/" использовать в видимых именах файлов в файндере, а в файловой системе это ":". Тоже раздолье, спасает только то, что мало кто так файлы называет.
VBKesha
02.05.2024 13:31Пару месяцев назад помогал другу решать проблему с русским путем, для какойто Java IDE не помню уже какой. Не хотела работать если в имени пользователя были русские буквы(темп она там хранила). Оказалось была в системе выбрана англиская кодировка для приложений не Unicode.
Panzerschrek
02.05.2024 13:31+1Какая-нибудь Visual Studio до сих пор наровит сохранить файл в CP1251, если там кириллические символы затесались.
mayorovp
02.05.2024 13:31Она делает интереснее: сохраняет в utf-8 если кириллические символы были сразу, и в системной кодировке если их не было. Когда они появляются - кодировка файла не меняется (сама кодировка определяется по наличию BOM).
Panzerschrek
02.05.2024 13:31+2Ну да, поэтому я если надо, сохраняю фал с кириллицей через Notepad++, а уже потом открываю его в Visual Studio.
Но меня удручает, почему студия всё ещё лезет за какой-то там системной кодировкой. И вообще не ясно, зачем эта системная кодировка нужна, почему там всё по умолчанию не в UTF-8.
deelayka
02.05.2024 13:31+1От этого спасает файл .editorconfig в корне проекта, в нём можно задать кодировку и окончания строк для файлов по умолчанию, студия его понимает. Ну и компилятору добавить опцию /utf-8, чтобы исходники трактовались как UTF-8 без необходимости добавлять BOM.
perfect_genius
02.05.2024 13:31А ещё Visual Studio до сих пор не умеет показывать кириллические символы в подсказках при наведении курсора на #define. Если определить #define тэст тэст2, при наведении на "тэст" высветится #define тэст \u0442\u044d\u0441\u04422.
LorHobbit
02.05.2024 13:31Надо же, а мне всего 2 года назад (не 20) прислали для отладки моего редактора контактов файл .VCF, сгенерированный аутлуком. И там, внезапно, оказалась cp1251. Хотя во всех RFC на vCard прописан UTF8.
Разработчики Qt вот тоже, видимо, 20 лет бнопни не видели. И в Qt 6.0 выкинули все неюникодные кодировки, а заодно и класс QTextCodec перевели в устаревшие. Уже в 6.4 их убедили вернуть поддержку всего, что поддерживает ICU... но уже в новые классы, которые изначально были заточены только на юникод. Козу купили, козу продали, но несовместимостей успели наломать.
Harwest
02.05.2024 13:31+1Автор поста ещё почему-то отсчитывает номера знакомест в двоичных числах слева направо.
makapohmgn
02.05.2024 13:31+3Вот буквально сегодня firefox показал мне кракозябры вместо русского текста в текстовых файлах, причём раньше в меню Вид можно было выбрать кодировку руками, но они это убрали почему-то)
vadimr
02.05.2024 13:31+25Настолько удивительная статья, что пришлось посмотреть биографию автора. Она всё и объясняет. Англичанин, год назад переехавший в Финляндию. Человек открыл для себя мир за пределами 26 букв.
Однако, поддерживаю предыдущего оратора: зачем это было переводить на русский?
Antra
02.05.2024 13:31+6Англичанин, год назад переехавший в Финляндию
переживает, что
€
невозможно никак представить в KOI8."Мне бы его проблемы" (c)
zaiats_2k
02.05.2024 13:31+3Вот англичанин, молодец, рассматривает вариант что придётся в Финляндии к KOI8 привыкать. А финны как бессмерные себя ведут. ;)
buratino
02.05.2024 13:31+7Сложности возникают с однобайтовыми кодировками, не относящимися к Unicode. Например, сложно отличить Win-1252 от KOI8, ведь для кодирования разных вещей и та, и другая используют обычно пустой первый бит ASCII.
пустой, да еще и первый бит...
По умолчанию, в большинстве конфигураций Excel сохраняет CSV в кодировке Win-1252.
аааа
Win-1252 — это однобайтная кодировка, не относящаяся к Unicode. Это расширение ASCII, засовывающее в неиспользованный восьмой бит достаточно большое количество символов для почти каждого европейского языка.
теперь этот пустой первый бит стал восьмым и неиспользуемым...
edrokov
02.05.2024 13:31+3Примерно 25 лет назад я попробовал натренировать самодельную нейросеть на распознавание кодировок. Обучалась на пентиуме примерно пару часов. Определяла на удивление точно по 20 символам текста.
mefepe
02.05.2024 13:31Проблемы разных стандартов только в том, что всегда найдётся группа альтернативно одарённых, которые вместо доработки пишут что-то своё с нуля...
perfect_genius
02.05.2024 13:31+2Unicode возник не так? Т.е. одарённые не взяли ASCII и не расширили её?
Panzerschrek
02.05.2024 13:31+1Вот кстати, а какой толк сейчас использовать UTF-16 (обе версии)?
Насколько я понимаю, 16-битные кодировки родились тогда, когда наивно полагали, что 16 бит хватит всем. Тогда же появился wchar_t, до сих пор использующийся в WinAPI. Тогда же в Java и JavaScript строки сделали 16-битными.
Но потом оказалось, что таки нужно сильно больше символов, чем полагалось ранее и UTF-16 стало не хватать, из-за чего появились суррогатные пары. В этом свете не понятно, какие ещё преимущества осталось у этого способа представления - он требует больше памяти в сравнении с UTF-8 для в основном латинских текстов, да к тому же существует проблема порядка байт.mayorovp
02.05.2024 13:31+1Вот кстати, а какой толк сейчас использовать UTF-16 (обе версии)?
Вот вы сами и ответили на свой вопрос: wchar_t до сих пор используется в WinAPI
LAutour
02.05.2024 13:31+2какие ещё преимущества осталось у этого способа представления
Всегда быстрый произвольный доступ к символам по их индексам\смещению, в отличии от UTF-8.
datacompboy
02.05.2024 13:31+4В том и дело, что с момента появления суррогатных пар -- это больше не произвольный доступ к символам по индексам. Больше нельзя узнать длину строки в символах через длину в байтах.
Вам следует перечитать диссертацию "О роли музыкальных инструментов в жизни домашних животных".
Zara6502
02.05.2024 13:31Равны единице, или «включены» только второй и последний биты
блин, ну даже если это перевод, то можно же было исправить ошибку с нумерацией битов, они считаются справа налево.
slonopotamus
02.05.2024 13:31+3Я извиняюсь, с какой стороны у байта лево?
salnicoff
02.05.2024 13:31+4Операция SHL производит сдвиг битов в сторону старших, значит, там и лево.
vikarti
02.05.2024 13:31Здесь всё довольно просто; по крайней мере, в вопросе числа, обозначающего «A», в целом есть консенсус.
Да? Точно есть? :)
А ничего что https://util.unicode.org/UnicodeJsps/character.jsp?a=0410 это «А»? И визуально отличить А от A - не очень просто.
vanxant
02.05.2024 13:31+1Есть ещё заглавная греческая альфа, она тоже выглядит как А. В большинстве шрифтов это один и тот же глиф (векторный рисунок)
AntonLarinLive
02.05.2024 13:31+1ФНС до сих пор все свои форматы клепает в расово-верном православном win-1251. Про UTF-8 они видимо не слышали. И всё бы хорошо, но только до первого клиента с национальными буквами в названии, не попадающими в 1251.
ps1961
02.05.2024 13:31+3Вообще-то вместе с WIN1252 стоило бы упомянуть что у Windows в GUI одна кодировка, в cmd другая. Для примера в русской обычно стоит 1251, а в консоли 866. Что кое когда приводит к очень забавным случаям
perfect_genius
02.05.2024 13:31Тем временем самый популярный браузер не даёт просто скопировать ссылку на какую-нибудь страницу самой популярной интэрнэт-энциклопедии и вставить её в нормальном виде.
Это такой позор, что слов нет.
Читал, что Опера определяла такой момент и не уродовала ссылку.
Evengard
Вспоминается вот этот вот ресурс: https://2cyr.com/decode/
А нет чего-то open-source-ного для этого?
okhsunrog
Есть утилита iconv. Да и в том же vim/neovim можно поменять кодировку. Писал как-то пост про это в свой тг канал, если интересно - скину вам сообщением ссылку на пост. В принципе, всё это можно и самому нагуглить, просто у меня более "разжёвано".
З.Ы. Статью смотрел по диагонали. У iconv нет автоопределения кодировки, но можно перебрать 2-3 самые распространённые кодировки в рунете - с большой вероятностью будет одна из них. А можно обернуть iconv в простенький python-скрипт, который будет фрагмент текста переводить используя разные кодировки, останется лишь выбрать кодировку, после которой текст больше всего похож на правду.
datacompboy
++enc=cp1251
уже требует пост в тг канал?okhsunrog
В принципе, всё сводится к :e ++enc=cp1251 и :w ++enc=utf-8 Я всего лишь подобнее описал работу с iconv из консоли и преобразование в UTF-8 всех файлов в заданной директории и во всех вложенных директориях рекурсивно. Встала такая задача полгода назад, и родился пост, поделиться опытом, так сказать. Понятно, что опытным юзерам тут всё понятно как 2 пальца, я больше для новичков писал :)
datacompboy
Родился пост, в неиндексируемом месте, там где никто никогда не увидит... Чтобы что? :)
okhsunrog
Хороший вопрос :) Ну, канал с заметками, человек 100 увидели. На статьи для Хабра такие заметки не тянут. Возможно, стоит попробовать новую фичу Хабра – посты. А вот интересно, они индексируются Яндексом/Гуглом?
datacompboy
Да
roqin
Ну вот ХЗ, всю жизнь (когда мне было лень указать явно кодировку, т.е. достаточно часто) - я использую enconv и не жужжу (какие-то самописные скрипты не нужны).
LorHobbit
Кроме iconv, есть ещё enca. Iconv есть в любом линуксе, а enca надо ещё поискать и скорее всего собрать, зато в нём есть АВТООПРЕДЕЛЕНИЕ! Я как-то в рамках рефакторинга очень-очень старого легаси-проекта (часть авторов писали в 1251, часть в КОИ8) натравил на дерево исходников связку find и enca, и она успешно всё перевела в UTF8.
Вот, кажется оно (хотя я пользовался бинарной сборкой одного из отечественных дистрибутивов):
https://github.com/nijel/enca
BasilioCat
Уже никому не нужны странные кодировки, все сожрал UTF-8 (иногда UTF-16). Лет 10 назад были chardet/chardetect/enca
Dotarev
Я давеча из-под Win 11 запустил bat файл, в ключ программы передал текст на русском. И очень удивился. Вот как вы думаете, в какой кодировке должен быть bat, чтобы ключ был передан корректно в .net приложение?
DMGarikk
866? консоль винды насколько я помню довольно забавная штука
Dotarev
Bingo!
fddima
Так, как настроена система. Есть же понятие ACP (ANSI Code Page). Вот в региональных настройках можно выбрать желаемую, щас по моему флажок для utf8 есть. Можно в реестре по старинке. Кодовая страница для utf8 - 65001.
cmd-файлы в ansi и соотв должны следовать настройкам системы.
Dotarev
Спасибо, теперь я знаю про команду chcp.
fekrado
А я вспомнил shtirlitz . Хорошая программа была
exTvr
О даа, восстановить/подобрать кодировку в закраказябленном письме!
vagonovozhaty
https://habr.com/ru/articles/147843/
demoth
Есть uchardet, которая для длинных текстов довольно неплохо определяет кодировку.