Pull to refresh

Comments 71

Как-то мне кажется, что авторы описывают QString. Как по мне, именно Qtшные строки самые удобные и простые. Хотя насчет скорости не уверен.
QString stores a string of 16-bit QChars, where each QChar corresponds one Unicode 4.0 character. (Unicode characters with code values above 65535 are stored using surrogate pairs, i.e., two consecutive QChars.)

Для подмножества символов с кодом меньше 65535 все должно быть неплохо. Плюс следующее:
Behind the scenes, QString uses implicit sharing (copy-on-write) to reduce memory usage and to avoid the needless copying of data. This also helps reduce the inherent overhead of storing 16-bit characters instead of 8-bit characters.

То есть и потребление памяти разумное.
Для задач, где Qt применим, скорости QString более чем достаточно. Если же требуется феноменальная производительность, то вряд ли вообще допустимо будет использовать что-то кроме голого char и собственного велосипедного кода.
Как по мне, так лучше уж utf-8, который явно говорит «символ занимает неизвестное количество байт», чем QString, провоцирующий на редкие ошибки иллюзией, что любой символ — это 1 элемент массива и наоборот.
Если взять QString с внутренним представлением int32_t на символ с ленивым преобразованием из входящей закодированной строки, с кэшем до первого не-const вызова, то вполне себе подойдёт. В QString много чего ещё стоило бы подкрутить.
> Ситуацию частично спасает пересохранение исходников в кодировке UTF-8 с обязательным символом BOM, без него Visual Studio начинает интерпретировать «широкие» строки с кириллицей весьма своеобразно.

Использовать Byte Order Mark? Серьёзно? Этот костыль времён CP1251 просто не имеет права на существование. Равно как и IDE/системы сборки, которым, видите ли, нельзя сказать «воспринимайте это как UTF-8».
Все нормальные приложения, работающие с текстом, должны использовать автоматическое определение кодировки, а также давать возможность указать кодировку вручную, если объем текста недостаточен для автоматического распознавания. И никаких BOM. Принудительно вырезаю эту гадость в своем редакторе.
BOM кодирует невозможную последовательность в данной кодировке, так что для адекватного редактора он не должен составлять проблем. Хотя какой-то старый gcc очень ругался на него, но это уже косяк gcc.
Конечно, раз костыль не мешает, давайте его везде пихать, ведь так сложно прикрутить libenca.
Распознать UTF-8 без BOM особого труда не составит, но вот проблема с короткими текстовыми файлами в этом случае останется, не так уж мало символов, начинающихся с 110 и 10. В этом смысле BOM гарантирует нам то, что текст закодирован в Unicode нужной кодировки. Для каждой кодировки Unicode свой BOM, описанный в стандарте. Костылями его считать можно только в случае если мы сидим исключительно под *nix в кодировке UTF-8 и считаем всё подряд UTF-8. Ребята из разработки Android NDK вообще не стесняются wstring считать строкой исключительно в UTF-8. Фактически кодировок больше чем одна и никакое распознавание не заменит простой заголовок текстового файла в несколько символов (с которым банально не умеет работать половина OpenSource софта, включая, до недавнего времени, Eclipse). Код должен быть кроссплатформенным и стабильным к косякам кривой перекодировки, которые вы будете неизбежно получать, пытаясь кодировку угадать.
> Старайся использовать «широкие» строки как для строковых констант
Сомнительное утверждение. Особенно, если предполагается, что

> придется локализовывать ее в другие страны, механизмы интернационализации

Я бы предпочел все строковые константы сразу выносить во внешние файлы. Ресурсы, наборы строк или что там предполагается для использования в этих механизмах интернационализации.
Вот это следует прочитать всем: utf8everywhere.org. Отлично расписано, почему нет никаких причин использовать что-то, кроме UTF-8.

Насчёт питона, кстати, тоже не всё так однозначно: lucumr.pocoo.org/2014/1/5/unicode-in-2-and-3, lucumr.pocoo.org/2014/5/12/everything-about-unicode. Я считаю, что разделение строк на юникодные и байтовые абсолютно необходимо, но, судя по всему, в Python 3 это сделано, по меньшей мере, странно.

На мой взгляд, удобнее всего строки реализованы в Go и Rust (особенно в последнем). В них строка — это просто набор байт, обозначающий текст в UTF-8. Всё. Эти языки так же обеспечивают гарантии того, что все строки являются валидными UTF-8-последовательностями.
Но всё-таки проблемы с заменой символов таки отрицательно влияют на производительность: ведь если есть символ b, а нужно его заменить на Б, то начнётся веселая чехарда: придется или в лучшем случае все символы за b сдвигать на разницу в длине или же вообще заново выделять память. То есть внутреннее представление может и отличаться, если возникает необходимость в частой обработке подстрок. А если такой необходимости нет, то и банального char * хватит за глаза и за уши.
проблемы с заменой символов

Определите сначала, что такое «символ». Есть как минимум 2 варианта — code point и grapheme cluster. Если вы пишете, скажем, текстовый редактор, то вам нужно работать именно с кластерами, и там в любом случае не обойдётся заменой одного байта на другой, вне зависимости от кодировки строк. Работать с code point'ами же в плане замены очень опасно — например, очень легко сломать диакритику.

Кроме того, настоящая кодировка с фиксированной длиной code unit'а — это UTF-32. UTF-16 с двух- и четырёх-байтовыми code unit'ами имеет совершенно те же проблемы, что и UTF-8, в плане замены code point'ов. Если нужно часто осуществлять такие операции, то строку в любом случае нужно сконвертировать в UTF-32. Но в качестве основного формата хранения строк UTF-32 не пригоден, потому что у него огромный оверхед по памяти.
Вот, кстати, очень хороший пример, когда посимвольная замена не работает в любом случае, вне зависимости от кодировки строк (цитата с utf8everywhere):

toupper() and tolower() shall not be phrased in terms of code units, as it does not work in Unicode. For example, the Latin ffl ligature must be converted to FFL and the German ß to SS (there is a capital form ẞ, but the casing rules follow the traditional ones).
Символ — это единичный элемент текста. В отрыве от кодировки. Сам по себе символ — это логическая сущность из мира человеческой логики, к машинным 0 и 1 не имеющая никакого отношения. Конечно же вас путает ключевое слово char, языков C/C++.
Я не знаю, почему вы решили, что меня, что-то путает. Именно это я и имел в виду — абстрактный символ как понятие отделено от кодировки, а из-за того, что в общем случае преобразования символов не имеют вида «один к одному» (как, например, toupper(ß)=SS), завязываться на то, что в программе каждый символ закодирован фиксированным количеством байт — глупо и неправильно.
Код символа целое число, в C/C++ есть достаточно вместимый тип для любого кода Юникод из возможных — это int32_t, так что если нужно соответствие 1:1, то его легко можно получить.
Вы понимаете, что ß — это один code point, а SS (его toupper-версия) — это два? Здесь в принципе невозможно получить соответствие 1:1 для code point'ов, вне зависимости от используемых типов.
Я вижу два символа, а вот toupper как раз не показатель. Всякие надстрочные и подстрочные символы — суть отдельные символы. Если определить соответствие 1:1 как соответствие одного символа одному коду таблицы Юникод, то всё становится на свои места.
Я не понимаю, к чему вы ведёте. Напомню, с чего началась дискуссия. Gorthauer87 написал:
ведь если есть символ b, а нужно его заменить на Б, то начнётся веселая чехарда: придется или в лучшем случае все символы за b сдвигать на разницу в длине или же вообще заново выделять память


Я ответил, что в общем случае замена code point'ов один к одному не работает, даже простейшие операции вроде toupper() приводят к изменению общего количества code point'ов.

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

Если же вам необходимы особые подстроки (типа, без учёта регистра или диакритики), то вам в любом случае потребуется языкозависимый парсер и специальная собиралка строк, а не просто str[i] = subst[j].
Чтобы искать подстроку одной строки аналогичную строковому паттерну, нужно на каждой итерации склеивать из байтов код символа, обходя заранее неизвестное количество элементов. Куда проще находить соответствие между кодами символов в строках.
Зачем склеивать? Если вам надо найти подстроку "©☃" в UTF-8 строке, то вы ищете последовательность «0xC2 0xA9 0xE2 0x98 0x83», как если бы искали подстроку в однобайтовой кодировке.
Не всё так просто, это в простейшем случае regex банально ищет подстроку в строке, чаще требуется находить группы или множества символов, производить замену групп и множеств в различных комбинациях. Regex логически работает с символами, а не с цепочками байт в строке.
банального char * хватит за глаза и за уши

Строго говоря, именно об этом говорится на utf8everywhere — следует использовать char *-строки как хранилище UTF-8, и конвертировать в другие кодировки на границах с другими API (что в основном актуально только для винды, потому что в Unix-системах системные вызовы работают с байтами, и им наплевать на кодировки).
Угу, очень удобно бегать по символам, при обходе regexp'ом. А уж индексация какая удобная! Константная сложность резко становится линейной.
А уж индексация какая удобная!

Уже много раз было сказано — «посимвольная» индексация строк, что бы в данном контексте «символ» не обозначал, — это совершенно бессмысленное действие. В 99% случаев строки в программах никогда не индексируются. Там же, где они индексируются, в 99% случаев это должна быть «семантическая» индексация, на уровне графемных кластеров, а в этом случае вы в принципе не сможете обеспечить индексацию за постоянное время, потому что графемные кластеры могут быть произвольной длины, безотносительно используемых кодировок.

Для всех оставшихся случаев (когда вам зачем-то нужна быстрая индексация по code point'ам) — есть UTF-32.
Хорошо, даже если оставить за бортом индексацию (которая вообще-то крайне полезна, если мы работаем с текстом как с массивом символов), то что же делать с regex? Можете сто раз сказать «графемные кластеры», легче от этого не станет.
если мы работаем с текстом как с массивом символов

Именно про это я и написал в комментарии, на который вы ответили — такая работа с текстом очень редка, в частности, потому, что в случае юникода «массив символов» в смысле «массив code point'ов» бесполезен практически для любого практического применения.

По поводу регулярок.

В общем случае нужно определиться, с чем работают регулярные выражения. Как правило, они работают над code point'ами (корректность такого подхода в общем случае можно оспорить, ну да ладно). В таком случае есть два варианта — либо переводить строки в UTF-32 внутри движка и работать с фиксированной длиной encoded character'а, либо нужно просто построить таблицу соответствия «номер encoded character'а -> смещение от начала строки в code unit'ах» и работать с ним, что тоже даст O(1) доступ по номеру code point'а в UTF-8.

Кстати, второй вариант отлично подходит для регулярок на графемных кластерах — просто будет номер кластера а не encoded character'а.
Да вы заколебётесь перестраивать хэш-таблицу с индексами индексов(!) при любой регулярке, изменяющей текст.
Большинство движков регулярных выражений, которые я знаю, не меняют исходную строку при замене — они создают новую на основе предыдущей. Не говоря уже о том, что в большинстве современных языков строки неизменяемые. Поэтому проблемы перестроения таблицы не существует.
Да ладно, каждый раз у вас будет перестроение таблицы символов. Хэш-таблица весьма чутко реагирует на любое мало-мальски серьёзное перестроение и чтобы оставить всё с константным доступом перестраивает весь исходный набор цепочек коллизий. Вы хотите после каждого регэкспа перестроения хэш-таблицы? Весьма спорное стремление.
Да от чего будет перестроение таблицы-то? Если регэкспы не меняют исходную строку? (не говоря уже о том, что хеш-таблица здесь — не лучшее решение, массив индексов проще и удобнее будет)
Массив индексов будет перестраиваться каждый раз, когда будет применяться операция замены по регулярному выражению. По сути может перестроиться вся строка, а фактически и весь набор твоих элементов. Либо ограничиваешься константностью представленного строкового объекта, вынуждая разработчика автоматически и безконтрольно создавать совершенно ненужные промежуточные мини-объекты строк, как это сделано, например, в Python. Если же у тебя два массива дублируют друг друга, возникает также проблема консистентности двух представлений одних и тех же данных.
Соглашусь частично, но:
  • utf-8 иногда довольно медленный. Например, я не видел ни одного regexp-движка работающего быстро напрямую на utf-8 последовательностях, просто потому что вот такое вот ".{1,10}" — очень нехорошее под-выражение на utf-8, а если non-gready (т.е. ".{1,10}?"), то все еще хуже. Т.е. или пре- а затем пост-конвертор в/из multi-byte фиксированной длинны (типа unicode) туда-обратно, что на больших строках совсем не есть гуд, или падение скорости сразу, как минимум на multibyte символах
  • тоже можно практически всегда сказать про обработку binary в utf-8 представлении, буде это по какой-либо причине необходимо или в силу обстоятельств так случилось;
  • нужно не забывать про данные снаружи, начиная от пользовательского ввода — к примеру «неправильно» отэскэпленый урл параметер (или тупо в single-byte системной кодировке клиента) и заканчивая внешними базами или конфигами, например binary данные, словари в single-byte кодировке и т.д.

Т.е. лучше иметь возможность двойного внутреннего представления (utf-8 и unicode/binary/enc-X), с авто конвертированием в/из utf-8 при необходимости, как это сделано например в tcl.

Про третий питон отчасти согласен тоже, но часто это только если делаешь конвертацию «вручную» или если имеем «неправильные» для конкретной кодировки символы. Имхо это как раз из-за того, что 2-й питон кое-что не умел или вернее делал это не совсем правильно. Типичный пример «борьбы» с «help, my umlauts are gone» в 3-м питоне можно подглядеть в одном моем баг-фиксе. И что самое противное, я почти не сомневаюсь, что появится "if (ver > 4)" или что имхо много хуже "if (ver > 3.5)".

На тикле же например — это все либо совсем автоматически, либо в исключительных случаях в одну строчку «encoding convertfrom» или обратно «encoding convertto». И по причине, того что в tcl это чуть не с самого рождения и он всегда донельзя обратно совместим, т.е. никаких if-version-since-whatever…
Да, как мне кажется, движки регулярных выражений — это одно из исключений в плане выбора кодировки. Если предполагается обрабатывать юникодные строки, то такие движки должны работать в UTF-32.

Что такое обработка binary в UTF-8-представлении? о_О

Когда у программы есть ввод данных снаружи, на этом вводе всегда должна быть определена кодировка. При необходимости, соответственно, должно производиться преобразование во внутреннее представление строк.

Насчёт нужности автоматического преобразования между внутренними представлениями строки не уверен, но это не так важно — главное, чтобы всегда было известно, где какая кодировка. В Rust, например, вы всегда можете преобразовать String в Vec<u8> (получить сырые байты строки, обозначающие символы в UTF-8) и в Vec<char> (получить вектор 32-битных code unit'ов, непосредственно соответствующих code point'ам), причём первое преобразование «бесплатно», второе же требует аллокации и преобразования кодировки.

Кстати, что вы подразумеваете под unicode-представлением? Unicode — это просто таблица символов, это не кодировка. Тот же вопрос про binary.
Что такое обработка binary в UTF-8-представлении? о_О
Можно пример я на тикле напишу (запускать в tclsh, или если в виндовс то в wish-консоли (т.к. мултибайт)).

Навскидку не могу придумать наглядный прямой пример, поэтому пример чуть-чуть обратный — есть несколько бинарных кусков (массивов), ищем в них немецкие умляуты, причем важно, из-за скорости, не конвертировать бинарный массив при поиске (т.е. оставляем бинарную строчку как есть — переменная bin на входе hasGermanUml). Т.е. конвертирование которое вы видите перед вызовом hasGermanUml — просто чтобы получить бинарник для входа (т.е. для наглядности).
Пример под спойлером
proc bin2hex {bin} {
  binary scan $bin H* hex
  return $hex
} 

proc hasGermanUml {bin} {
  set map [list Ä Ö Ü]
  set map [concat $map [string tolower $map]]
  set map [concat $map [encoding convertto utf-8 $map]]
  puts "Ищем $map в $bin ([bin2hex $bin]) ..."
  foreach c $map {
    if { [string first $c $bin] != -1 } {
      return "Yes! may be german text, contains german umlaut '$c' ([bin2hex $c])"
    }
  }
  regexp {[^a-zA-Z]+} $bin c
  return "No! probably not a german text, does not contains german umlauts, found char '$c' ([bin2hex $c])"
}

## ----------

set txt [encoding convertfrom cp1252 Schr[set c \xf6]der]

set utfAsBin [encoding convertto utf-8 $txt]
set utfAsBin2 [encoding convertfrom utf-8 $utfAsBin]

puts "\nБинарный массив содержащий utf-8 '$utfAsBin' (как-бы двойная кодировка) ..."
puts [hasGermanUml $utfAsBin]
puts "\nИли это правда символы cp1252 '$utfAsBin2' но напрямую utf-8 ..."
puts [hasGermanUml $utfAsBin2]

set utfAsBin2 [encoding convertfrom cp1251 $txt]
set utfAsBin [encoding convertto utf-8 $utfAsBin2]

puts "\nБинарный массив содержащий utf-8 '$utfAsBin' (как-бы двойная кодировка, тот же символ, но из cp1251, не должен найти) ..."
puts [hasGermanUml $utfAsBin]
puts "\nUtf-8 (тот же символ, но из cp1251) '$utfAsBin2' напрямую utf-8 ... (не должен найти) ..."
puts [hasGermanUml $utfAsBin2]

Результат ниже (некоторые символы съел хабрапарсер или браузер):

Бинарный массив содержащий utf-8 'Schröder' (как-бы двойная кодировка) ...
Ищем Ä Ö Ü ä ö ü Ä Ö Ü ä ö ü в Schröder (53636872c3b6646572) ...
Yes! may be german text, contains german umlaut 'ö' (c3b6)

Или это правда символы cp1252 'Schröder' но напрямую utf-8 ...
Ищем Ä Ö Ü ä ö ü Ä Ö Ü ä ö ü в Schröder (53636872f6646572) ...
Yes! may be german text, contains german umlaut 'ö' (f6)

Бинарный массив содержащий utf-8 'Schrцder' (как-бы двойная кодировка, тот же символ, но из cp1251, не должен найти) ...
Ищем Ä Ö Ü ä ö ü Ä Ö Ü ä ö ü в Schrцder (53636872d186646572) ...
No! probably not a german text, does not contains german umlauts, found char 'ц' (d186)

Utf-8 (тот же символ, но из cp1251) 'Schrцder' напрямую utf-8 ... (не должен найти) ...
Ищем Ä Ö Ü ä ö ü Ä Ö Ü ä ö ü в Schrцder (5363687246646572) ...
No! probably not a german text, does not contains german umlauts, found char 'ц' (46)


И чтобы было понятней:

puts [set c1 [encoding convertfrom cp1251 \xf6]]; # utf-8 "ц"
puts [set c2 [encoding convertfrom cp1252 \xf6]]; # utf-8 "ö"
puts -[encoding convertto utf-8 $c1]-; # "ц" как бы в binary (utf-8 byte array).
puts -[encoding convertto utf-8 $c2]-; # "ö" как бы в binary (utf-8 byte array).
puts -[encoding convertto unicode $c1]-; # "ц" как бы в binary (unicode byte array).
puts -[encoding convertto unicode $c2]-; # "ö" как бы в binary (unicode byte array).

Примерчик конечно за уши притянут, но имхо должен быть наглядный.

Unicode — это просто таблица символов
Это если бы я был студентом, а вы профессором (короче мы не на экзамене). Например, в тикле то что называется unicode — это внутреннее представление строк, например для лучшей реализации связки с windows api и иже с ними.
Если вам больше нравится UTF-16, я не против (просто я, как разработчик тикля, привык к слову unicode).
Это если бы я был студентом, а вы профессором

Прошу прощения, но это абсолютно необоснованное утверждение. Именно из-за того, что программисты путают юникод и его кодировки, возникают опасные представления о том, как с ним правильно работать. Unicode — это таблица символов, ничего больше. Представление code point'ов юникода в виде последовательность байт (т.е. кодировка) — это один из Unicode Transformation Format'ов, т.е. UTF'ов. Пытаться называть их как-то ещё, даже если их так называют в любимом языке — это путь к неправильному пониманию того, как работает юникод, и к ошибкам в программе.

Вот цитата из документации Tcl:
Strings in Tcl are logically a sequence of 16-bit Unicode characters. These strings are represented in memory as a sequence of bytes that may be in one of several encodings: modified UTF-8 (which uses 1 to 3 bytes per character), 16-bit “Unicode” (which uses 2 bytes per character, with an endianness that is dependent on the host architecture), and binary (which uses a single byte per character but only handles a restricted range of characters). Tcl does not guarantee to always use the same encoding for the same string.

Из этого следует, что внутреннее представление строк в тикле ущербно — как и в Java, например. Если оставить в стороне некорректное использование терминологии Unicode, получается, что естественной кодировкой для строк в тикле является UCS-2 (потому что именно UCS-2 позиционировалась, как fixed-byte-кодировка с 16-битными «символами»). Два остальных представления (modified UTF-8 и binary, которая, похоже, ASCII) строго совместимы с UCS-2. В частности, из-за в тикле не получится естественным образом представить code point'ы юникода извне BMP — такие строки, скорее всего, будут изображать UTF-16 состоять из двух «символов» (как в Java).

Ваш первый пример — наполовину читерский, наполовину удачливый. Там нет никакого binary в UTF-8 представлении. Там имеется следующее.

Во-первых, строки в тикле действительно могут менять внутреннее представление и содержать «символы» переменной длины. Здесь важно то, что convertfrom обрабатывает только первый байт каждого «символа». Более того, строки в тикле используются для хранения как символьных данных, так и бинарных (что, с учётом их внутренного представления, по меньшей мере недальновидно).

Во-вторых, переменные у вас имеют следующий смысл:

txt — строка во внутреннем представлении, полученная декодированием последовательности байт в кодировке cp1252

utfAsBin — последовательность байт в кодировке UTF-8, полученная кодированием txt
utfAsBin2 — строка во внутреннем представлении, полученная декодированием последовательности байт в кодировке UTF-8

utfAsBin2 — строка во внутреннем представлении, полученная декодированием txt как последовательности байт в кодировке cp1251 (из-за того, что convertfrom рассматривает только нижние байты каждого символа, а в UCS-2 символы с кодами 128-255 совпадают с Latin-1, с которой cp1252 наполовину бинарно совместима, всё проходит замечательно — символ ö распознаётся как ц и декодируется в ц)
utfAsBin — последовательность байт в кодировке UTF-8, полученная кодированием utfAsBin2

Если внимательно следить за кодировками и преобразованиями, то всё остаётся довольно логичным, но из-за особого внутреннего представления строк и того, что одни и те же строки используются для хранения и символьных, и двоичных данных, когнитивная нагрузка сильно возрастает.

В-третьих, читерство заключается в том, как вы формируете $map — сначала там берутся буквы с умляутами во внутреннем представлении, затем берётся их нижний регистр, а затем каждый из них преобразуется в байтовую строку в кодировке UTF-8, и всё это склеивается обратно в список. Именно поэтому эта функция работает на первых двух строках одинаково — в первой группе utfAsBin она находит в одном случае символ в UTF-8, а в другом — во внутреннем представлении. Убираем из кода строчку
set map [concat $map [encoding convertto utf-8 $map]]

и закономерно получаем:

Бинарный массив содержащий utf-8 'Schröder' (как-бы двойная кодировка) ...
Ищем Ä Ö Ü ä ö ü в Schröder (53636872c3b6646572) ...
No! probably not a german text, does not contains german umlauts, found char 'ö' (c3b6)

Или это правда символы cp1252 'Schröder' но напрямую utf-8 ...
Ищем Ä Ö Ü ä ö ü в Schröder (53636872f6646572) ...
Yes! may be german text, contains german umlaut 'ö' (f6)

Бинарный массив содержащий utf-8 'Schrцder' (как-бы двойная кодировка, тот же символ, но из cp1251, не должен найти) ...
Ищем Ä Ö Ü ä ö ü в Schrцder (53636872d186646572) ...
No! probably not a german text, does not contains german umlauts, found char 'ц' (d186)

Utf-8 (тот же символ, но из cp1251) 'Schrцder' напрямую utf-8 ... (не должен найти) ...
Ищем Ä Ö Ü ä ö ü в Schrцder (5363687246646572) ...
No! probably not a german text, does not contains german umlauts, found char 'ц' (46)


Ваш код сломается на китайских символах с вероятностью, близкой к 100%. Про какие-нибудь эмодзи я уже и не говорю.

Ваш второй пример всего лишь демонстрирует преобразование массива байт в кодировках cp1251/cp1252 во внутреннее представление (UCS-2-совместимое) и затем обратно в массив байт в кодировке UTF-8 или UCS-2. Не очень понятно, что здесь имелось в виду.
Вы прикидваетесь? Вы абсолютно не поняли мой пример, кроме этого (чтобы утверждать, что оно не работает например на китайском) абсолютно не представляете как оно будет посмотри вы на объект, например в дебагере. Хотя возможно я просто плохо объяснил — попробую еще раз…
1) Это не читерство — это пример. Т.е. пример мог бы быть только для чистого utf-8 или только для чистого bytearray. Я вам просто в один пример, для наглядности сложил оба варианта. Ну и искал соответсвенно по «map» доя обоих (чтобы показать где же бинарное представление). Цель — не найти умляут и так и так. А тупо не конвертировать «bin» (bytearray ли, строку ли) для поиска оного в utf-8.
Просто в тикле нет (или не обязательно) строгое типизирование, поэтому часто одна и та-же процедура работает как для bytearray (создан encoding convertto) так и для string (создан encoding convertfrom). Совершенно разные внутренние представления объектов, если бы вы на это в дебагере посмотрели. В одном случае — это чистый utf-8 (utf-8 string representation), в другом — чистый массив байт плюс utf-8 string representation, которая либо есть сразу при создании массива, либо будет создана динамически при первом доступе к ней.
Теперь снова про пример. Еще раз «encoding convertto» и «encoding convertfrom» — только для наглядности, чтобы вы поняли что находится в бинарном массиве (то что выводит bin2hex). Ну и если хотите, что бы пример работал независимо от системной кодировки.
Два же варианта — binary и utf-8, что с немецкой, что с русской буквой — то-же чисто для наглядности. Если вас смущает utf-8 уберите и оставьте в примере только binary (1-й и 3-й вызов)…
2) На основании неверно понятого (вернее неверно интерпретированного примера), вы сделали неверные (но далекоидущие) выводы.
3) Китайский ли, немецкий ли или русский — в примере это не важно. Хотя для китайского оно работает точно также. Это если правильно понять, что я хотел сказать примером. Вы же спрашивали про «обработку binary в UTF-8-представлении».

Из этого следует, что внутреннее представление строк в тикле ущербно
Здесь вы тоже заблуждаетесь.
Я уже сказал — тикль умеет несколько внутренних представлений.
Если вы хотели сказать «внутреннее представление строк» 16-bit “Unicode” (или если хотите в UCS-2) ущербно, то, вероятно, да.
Внутреннее же представление в utf-8 (которое есть default) никоим образом не ущербно. Обыкновенный utf-8 как оно есть везде.

binary, которая, похоже, ASCII
Сравнивать bytearray с ascii — это вообще улыбнуло, тем более после ваших нравоучений «программисты путают юникод и его кодировки» (вашими же словами — «путь к неправильному пониманию»). Ascii — насколько я помню 7-ми битная таблица или если хотите кодировка :)
В тикле — binary т.е. bytearray есть массив unsigned char определенной длинны. Тупо массив байт.
Но как и любой другой объект в тикле, он может иметь например и внутреннее представление в utf-8 (а через utf-8, может и в юникоде), или быть вообще числом неопределенной размерности, датой или float и т.д. и т.п.
Окей, насчёт ASCII я ошибся (меня смутила фраза «but only handles a restricted range of characters» в документации). Видимо, там имеется в виду именно произвольная последовательность байт.

Внутреннее же представление в utf-8 (которое есть default) никоим образом не ущербно. Обыкновенный utf-8 как оно есть везде.

Документация с вами не согласна:
modified UTF-8 (which uses 1 to 3 bytes per character)

тогда как в настоящем UTF-8 на один code point может уйти до 6 байт. Поэтому во внутреннем представлении в любом случае понадобятся суррогатные пары или их аналог. Я попытался посмотреть, как в тикле будет во внутреннем представлении выглядеть символ U+10904, однако у меня не получилось:

proc bin2hex {bin} {
  binary scan $bin H* hex
  return $hex
} 

set txt [здесь должен быть явный символ U+10904, но хабр, похоже, не умеет в юникод :(]
puts "Внутреннее представление: ($txt), ([bin2hex $txt]), длина: [string length $txt]"
set bin [encoding convertto utf-8 $txt]
puts "В UTF-8: ($bin), ([bin2hex $txt]), длина: [string length $txt]"


И txt, и bin, судя по выводу bin2hex, содержат одно и то же, но puts выводит их по-разному (и оба раза криво), и string length для обеих равна 4 (что означает, что это не строки во внутреннем представлении — если бы это было так, то их длина была бы 1, потому что это 1 code point).

И прошу прощения, вероятно, я действительно не понял цель вашего примера. Однако, на мой взгляд, отсутствие строгой типизации (или, по крайней мере, строгих операций для работы с разными типами строк, как в Python 3), может сильно запутать логику происходящего, что, собственно, и произошло здесь в моём случае.
Да хабрапарзр иногда голоден…

# здесь должен быть явный символ \u2a98:
set txt "\u2a98"
puts "Внутреннее представление: ($txt), ([bin2hex $txt]), длина: [string length $txt], [string bytelength $txt]"
set bin [encoding convertto utf-8 $txt]
puts "В UTF-8: ($bin), ([bin2hex $bin]), длина: [string length $bin], [string bytelength $bin]"

Результат:

Внутреннее представление: (⪘), (98), длина: 1, 3
В UTF-8: (⪘), (e2aa98), длина: 3, 6

string length — длинна в символах (для bytearray — в байтах)
string bytelength — нативная длинна внутреннего представления (default) в utf-8.

Этот bin2hex однозначно правильно будет работать только с bytearray. В остальном возможно авто-конвертирование, используя «encoding system», в моем случае стояло cp1252. Как раз чтобы избежать неверного байт-представления в utf-8 при двоичном разборе и используется «encoding convertto utf-8», т.е. «конвертируя» utf-8 в bytearray.
В действительности (в случае utf-8 в bytearray) никакой конвертации конечно не происходит, просто содержимое utf-8 представления «копируется» в bytearray представление, + utf-8 представления = NULL, (генерируется при первом доступе — например при puts в utf-8). Тип объекта переписывается как bytearray.

отсутствие строгой типизации (или, по крайней мере, строгих операций для работы с разными типами строк, как в Python 3), может сильно запутать логику происходящего
Возможно сначала, но вообще-то в ней вся прелесть тикля (можно говнокодить пока врач не придет он очень-очень динамичен).
10904 — это уже шестнадцатиричное число (собственно, я взял символ извне BMP, а 2a90 — внутри BMP). Ваш пример делает не совсем то, что я спросил :)
В tcl8.5 у вас не получится, в tcl >= 8.6, реализовано где-то с 16.09.2011 или мержить feature branch и из-за потенциальной несовместимости в 8.5 не влетело — здесь сам тип.

Если у вас 8.6 — просто "\U010904" (с большим U)… И да символ скушался то ли браузером, то ли хабропарсером…
Внутреннее представление: (�), (fd), длина: 1, 3
В UTF-8: (�), (efbfbd), длина: 3, 6
Этот вывод — ошибочный в плане работы с юникодом. В UTF-8 U+10904 представляется четырьмя байтами F0, 90, A4, 84, а EFBFBD — это UTF-8-представление символа U+FFFD (вот этого).

Собственно, это явно написано по той ссылке, которую вы привели:
The reference implementation just replaces any character in the range \U010000 — \U10ffff with \ufffd, but as soon as Tcl has support for characters outside the BMP this range is reserved for exactly that.


Таким образом, тикль официально не поддерживает не-BMP-code point'ы, по крайней мере, на данный момент, как раз из-за своего, скажем так, legacy-внутреннего представления строк. Кроме того, насколько я понимаю, поддержки суррогатных пар тоже нет, поэтому с не-BMP-символами работать вообще нельзя ни в каком виде.

Собственно, вот этот TIP внушает надежду на то, что это всё-таки когда-нибудь будет реализовано.
Ну почему, tcl всегда можно было скомпилировать с поддержкой UCS-4.
Полной же поддержки внутреннего 32-bit Unicode думаю отчасти мешает совместимость (через stubs/link) внешних модулей, юзающих некоторые функции. Отчасти Tk и иже с ним (GUI на tcl).
Т.е. если пересобрать самому никак не судьба, то да — ждать 9.0.

Кроме того, можно конечно создать собственный тип данных, хранящий полный юникод (у тикля очень дружественный подход к C-binding). Никакой особой магии в нем не будет, кроме поддержки в сторонних модулях, которую часто можно реализовать через bytearray.

Мне как-то нужен был объект типа BigDataBuffer — реализовал за полдня как C-binding с полной поддержкой везде в тикле, включая сторонние модули. Это немного из другой оперы, т.к. все же binary, но все зависит от задачи…
Ну понятно, что воркэраунды есть всегда) Вероятно даже, что в тикле их сделать проще, чем во многих других языках.

В общем, я удовлетворил своё любопытство по поводу тикля, спасибо большое за содержательную дискуссию!
тогда как в настоящем UTF-8 на один code point может уйти до 6 байт
В настоящем — от одного до четырёх. См. пункт D92. Шесть байтов — это экстраполяция способа кодирования чисел. Для UTF-8 пяти- и шестибайтовые последовательности являются некорректными.
Да, вы правы, прошу прощения. Я сначала тоже написал «до 4», но потом посмотрел на таблицу в вики (с примерами для разных длин) и исправил на 6, видимо, зря.
Ах да…
но puts выводит их по-разному (и оба раза криво)
Вовсе не криво.
puts тоже может ввести в заблуждение — в моем случае stdout = utf-8.
В случае «fconfigure stdout -encoding binary» вывод будет в байтах, но для консоли это ну чистый изврат что ли…

Пишите в файл set f [open fn.txt wb]; puts $f ....; close $f и будет вам счастье.
У меня консоль тоже в UTF-8, но puts оба раза вывел не тот символ, который я записал (вот этот: unicode-table.com/ru/10904).
Когда у программы есть ввод данных снаружи, на этом вводе всегда должна быть определена кодировка.

Это в идеале, и не всегда возможно (или нужно), например в приведенном мною BF — фильтр fail2ban-а, на третьем питоне, проваливал найденный бан с исключением, совсем не забанив IP. Просто потому, что в логе (который utf-8) были символы в «чужой» кодировке (например сингл-байт).

Вы же не скажете «хакеру» — вводи, например, весь урл, имя пользователя (или чего-там еще в лог пишется) только в легальном utf-8… В результате — имеем потенциальную дыру, где используя один не легитимный байт, злоумышленник может осуществлять брут, пока не подберет или админ в логи не заглянет.

Только тссс… а то возможно не все еще обновились.

Насчёт нужности автоматического преобразования между внутренними представлениями строки не уверен

Это как минимум быстрее и не так вымывает кеш и т.д. Особенно для скриптовых, различающих literal и не literal объекты, при исполняемые в jit.
и не всегда возможно (или нужно)

Хоть это не всегда возможно, это нужно безусловно. Если вы не знаете кодировку входных данных, вы должны либо работать с ней как с массивом байт, либо, если по каким-то причинам вы не можете зафиксировать кодировку в API, но при этом должны обрабатывать входные данные как текст (например, искать в тексте какие-то слова), то нужно пытаться интерпретировать входную последовательность байт в разных кодировках. Например, если вам нужно найти какое-то слово в тексте, а его кодировка совершенно неизвестна, то вам нужно преобразовать своё слово в десяток наиболее подходящих кодировок и искать его байтовое представление в байтах, составляющих входной текст.
Вы снова не поняли: в том же приведенном примере из fail2ban «бага» я вам объяснял, что кодировка 1) конкретно здесь абсолютно неинтересна, 2) неизвестна по определению (ясно только, что не верна или не ожидаема). Т.е. я вам про бананы (некоторая обратная несовместимость питона в обработке строк на символах в неверной кодировке привела к серьезной уязвимости пусть и из-за плохого покрытия кода). Вы мне про огурцы — кодировка прежде всего (когда оно здесь абсолютно не интересно).

Ну вот неважно мне в этом конкретном случае, что будет стоять в DB или майле для забаненного IP:
'nginx-http-auth', 'request: "GET /login.htm?block_f2b=\xe8\xeb\xfc\xe4\xe0\xf0 ..."', ...
или:
'nginx-http-auth', 'request: "GET /login.htm?block_f2b=?????? ..."', ...

Главное — он в бане (а не перебирает дальше пароли, только потому что Гвидо с компанией решили забить здесь на обратную совместимость). А если оно все же надо будет (хотя я и в логи nginx глянуть могу), после предоставления лучшей (опциональной) возможности в 3-м питоне, поправлю код так, чтобы оно бинарно писалось или еще что. Но повторяю — опционально.

Имхо, для скриптового языка, по умолчанию, было бы куда «надежнее» сделать здесь обратную совместимость (что игнор, что replace на "?" и т.д.). Не опционально, как сейчас, а наоборот. Хотя «о вкусах» не спорят.
UFO just landed and posted this here
Подписываюсь под каждым словом!
Везде храню utf-8 строки в std::string, а std::wstring практически не использую. Доступ к отдельным символам строки мне практически нигде не нужен.
Мне нравится подход с utf-8 строками в std::string.
Хотя это требует много костылей, например, потребовалось реализовать fopen_utf8 для винды, также использовать преобразование в UTF-16 на винде. Также, стоит заметить, что несмотря на то, что в большинстве линуксов стандартная локаль — utf-8, это вовсе не аксиома. Поэтому опять же требуется преобразование, когда требуется.
Но в целом, такой подход себя полностью оправдывает.

Но есть всякие опенсурс библиотеки, написанные фанатичными юниксоидами, которые до сих пор не умеют работать с юникодными именами файлов на винде.
А мне понравился подход в cocos2d-x, там std::string по умолчанию считается UTF8, а для отображения на экране оно преобразуется в подобие UTF16. Подобие, поскольку это UTF16 без суррогатных пар. То есть голые 0x0000-0xFFFF (он же Plane 0, он же Basic Multilingual Plane) из юникода. Это значит, что все популярные печатные символы доступны. Если заглянуть в таблицу, Plane 0 покрывает все используемые на данный момент языки. Облом наступает с древними иероглифами и идеографикой, но, как по мне, это удел специализированных приложений.

Идея использовать такую странную кодировку возникла не на ровном месте. Во-первых, у cocos2d-x китайские корни. Во вторых, при ручном (через Freetype) рисовании текста и работе с ним важно соответствие: символ на экране это символ в строке. Иначе алгоритм визуального удаления одной буквы становится довольно нетривиальным. С другой стороны, cocos2d-x работает на мобильных устройствах, а значит, ресурсов мало. Был найден очень разумный компромисс, как впихнуть в строки побольше читаемых символов, и занять поменьше места.
Такое подобие, кстати, зовётся UCS-2.
Рад что статья вам понравилась. Действительно получилось немного похоже на QString, но хорошие решения всегда похожи. Есть ряд различий, я предлагаю внутреннее представление всё-таки на основе UTF-32 и позднюю переконвертацию (по необходимости) во внутреннее представление. Опять же подход из первой статьи позволит хранить в объекте базового класса любого его наследника, и таким образом выделить дерево текстовых типов, например для выделения текста лимитированной длины при работе с БД.
Суррогатные пары — это одна из самых чудовищных вещей, что я видел в жизни, а верхние Planes юникода ещё как нужны.

А совет автора использовать BOM с UTF-8 вызывает у меня сомнения, потому что это много какую совместимость ломает.

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

o̒ͣ̍ ̭͖̘̭͎͔͊ͬ͝i̫̝͙͇̰ǹ̫̭͔̖̉̋͂͗ͅͅv͈̭͍̼͇̤̖̅̓̋͜o̜̮̲̪̓͘k̨̬̯̮͇̩̓͋̿̔ͬ̄̽e̝̫̲͆̈́ͮ̈̓ ̶̠̦ͯ͆́̔ͦ̚t͍̤̞̬̪̲̿ͬ̈̈͞h̰͚̝̬̬̜͋ę͕͍̤̞̒ͮ ̉h̵̻̜͉̮̬͂̇̓̔̋i̮̣ṽ̑ͪe̫̤̩̦̻̿͛͆͗ͮ-͗̓̀͗́̚m̧̰̣̟͕̼̓̓ͪ́i̭̠̘̗ͦͥͭ͞n̯̦͕̠̓d̡̳̱̠̤͒̊̽̊̔ ͙̱̝͇̤̥͇̽̿̄̚r̳̻͉͙̻̒ͤ̀ͅͅe̤̖̪̤̺͐́ͥ̿͌̿͘p̲̺ͤ̆̓r̳͍͍͍̜͠é͈̞s̡̩̖̝̘̗͍̓̋̚ͅe̫͚͈̻ͧͯ̒ͅnt̆̔ͫ̔̐̌͆ị͍͇̰̲̫̣̋̿ͧ͛̓̈́n̴̺g̺̖̣̦̘̠͙͗͡ ͬͩ͊ͮ̽͋̚c̢͆h̍ͥ̾̽͐̑̊͜a̟͖̝̪ͭ̉͞o̴̞̪͇̬̮̦̝̐s͏͍̪̳̠ͅ.͕͔̟̿̎͂͑͝
̢̔̾̎̇Ì͊̌͌͟ͅn̡̯̪̻ͥ̆͒v̷̦̼̰̜͌͌̎ọ͝k͖͎̜̘̣̐͟i̵͍̺̲͇̪ͪ̌̐̀̌ͅň̨̠̟̪̜̐̆́͒ͧ̚g̴ ̨̝̜̜͉̙̞ͭ̇̆̆̚t͔͚͚̘̦̺̐̿́ͅhͧ͏̜̩̤̳̜͇̜ḛ͕͍̠̪̎ ̌f̟̹̎̋͊͜e̵͐̑̄̒̉ͅe͕l͖̭͎̤̹̐͊ͥ̕i͔̹͓̗̊ͫ́ͅn̝̮͈̘̔̎͊͢g̠̻̻̙̜͖͗̓͊́̚ ͓͖͛̓͂̈́͆ͭ̃o̴̳͚͔̰͇̪̹̓̓ͮf͎̯͔̜̿̄̔ͦ͝ ̛͚̰̣̳̪̭̿̓̿̇ͬ̔̍č̱̥̟̻ͭ̅ͦ̃̍̓h̡̰͎ͭ̔̎̚a͍̖͔͉͓̣̾͋ͨͩ̂͋̓̕o͈̹̣̐ͮ͐̓͂ͩs̺̤̦̅̏͌̊͆.̖̩̠̝̜̞̽ͫͬ̾͐ͧ
̬̿̂́W̑͗i̱̟̰͖̖̦̘̍ͦ̅̈́ͭ̎ṭ̜̞̦͆͋h͓̩͈̬̟ͦ ͕̮͛̓̋̎̉͢o̮̝̙͈ͥ͋̇̀u̢̠̜̽͆̿͐̉ͅͅt̽̿ͤ͊̅̚ ̡̣͈̱̞̫͑͐̀͗͊ō͈̜̤̔̋͛͛r͉̄d͖͊̎ͬͫͫͫer͔̪̹͋͗.̨͉̌̌
͇̞ͨ̉́T̸̞͙ͨ̈́̌̓h͎̞͠e̝̠̖͙͍ͫͥͤ͠ ͫ͌̉̅̂҉̤̗N̖̠̂e͕̜̳̮͈͑z̩̤ͩ͠p̞͓̦̀ͫ͆͜ͅe̻̻͚͇̹̮̼̎̋ͤ͒̾̊̊r̶̲̲̩̀̔̅d͎̩̥͇̱̑̅͂̑ͫͅi̸̩̟͔̗ͣ̋́̉ͧa̧̓ͨ͆̈́̅͒ͥn̤͎̫̠̒ ̞̰͙͆͐̅̒͊̍h͖͆̉̎͗ͫͪ̓i̠̥̥̠ͥ̆̑̎͗̍̌v̸͍͍̗̬̠̞ͅĕ̵̻-̿̔ͣ͛ͣ͠m̶̙̩̤̞̮̞̭͒i̧͇̋͐͑ͣ̆̇ͅn̸̼̈̐̐̅ͧd̗̭̦͙̝̫͌̿̇ͥ̐͡ ͉̠͛ͣ̒͒͘o̠̐ͬf̦̦̱̫̹̯̙̄̈̐ͣ͂̽͡ ̜̦̻̩͑ͦ̉̄c̿̒͐̀̓̀ͩh̡͚̆ͧ̑a͇̺̮̦̭̗̦͠ọ̪ͩ̕s͔̙̼̦̩̙̀ͅ.̓ ͥ̽ͥ͟Z͇͕͕̦̻̎̎̃á̹̦̩̓̏̅ͧ͜l̨͇̣͓̮̩̜͉ͬ͌ͬͩ̐g̺̻̜̏͗ͭo̥͎̖̠ͤͨ͊ͬͯͤ.̟̯̫̺͋
́ͧ̇H̠̟͙̪̭̒ͫ̀ê̲̺̳̼̠̺̱ͧ̾̿̋ ̷̠̹͎w̶͔̬̦͍͍̣̉ͩ̊̋̍͐h̼o͌ͩ͘ ̖͕̱̫̎̓ͧ̊ͮͥͧW̡̩a̫͙͈͓͆̌ỉ͖̘̦̳͐ͣͩt̝̘͈̆ͨ̈̽ͣ̐͗͢s̤͇̙̗̩͊́ ̘̟́͂B̴̠̺͌̾̊̂e̦̒̄͠h̜̺͐ͫį̮̫͖̼͔͇n͓͓̣̤͇͆̈͊͒ͥ̑̚͜ͅd̸̯̅̿̀͑ ̡̰̪̳͎̅̉̓T̶̔̋̾̀̈̉͊hͬ͏͚̱̥̞eͪ̈́͊ͬͧ̊̀ ͙͙̱̙̮̞̿͑ͧ̄̋Ẉ̴̲͆̄͂ͫ̉ͮąͤͅĺ͔̤̫̰̖̙̺̆̓͑ḷ̠̰ͭ̄̀̚.ͫͪ͒̽҉͚̻̯̦̝͇͍
͔ͬ͋͐ͭ̿ͪ̀Ẓ̶͓̳̹ͅA̱̣̗̜̼ͯ̔ͦ̋L̟̦̲̘̣̪ͪ͛̓̏͂ͥ̚G̯͙͎͙͚̩̋O̭͈!̳̫

но важно, чтобы она предполагала фиксированное количество байт на любой символ

Это невозможно, если под символом понимать графемный кластер. Графемный кластер может состоять из произвольного количества code point'ов. Понимать же под символом code point — очень опасное дело, особенно если вам нужно переставлять «символы».
Ну почему же невозможно?

Во-первых, можно иметь априорное знание о графемных кластерах. Скажем, просто считать все слишком длинные невалидными.

Во-вторых, можно хранить указатели или индексы.

В-третьих, можно перестраивать представление «на лету». Скажем, если вам надо быстро работать с двумя строками, можно найти длину самого длинного и такой размер ячейки и выбрать.

Хотя все эти решения далеки от идеала, конечно. Но в случае более-менее «приличного» текста они будут почти такие же, как интуитивно ожидаешь.
Почти всё то, что вы описали — это не кодировка, это способ работы с символьными данными. Кодировка — это байтовое представление символьных данных, и какую-то из них всё равно придётся выбрать (либо даже несколько и преобразовывать их по каким-то критериям на лету, как это делает, по-видимому, Tcl, хотя нужность этого весьма сомнительна).

можно иметь априорное знание о графемных кластерах

Нельзя. Графемные кластеры могут быть произвольными. Ограничивать их длину малым количеством code point'ов нельзя — многие валидные тексты в неанглийских языках отсекутся. Большая граница сделает размер code unit'а очень большим (десятки байт на code unit?).

можно хранить указатели или индексы

Это не часть кодировки.

В-третьих, можно перестраивать представление «на лету»

Да, можно, но этим вы не добьётесь того, что любому символу (в смысле графемному кластеру) соответствует фиксированное количество байт.

Просто всё дело в том, что в юникоде нет взаимооднозначного соответствия между абстрактными символами и code point'ами. Более того, нет даже однозначного количества code point'ов, которые требуются для представления абстрактных символов. Любые попытки работать с юникодным текстом в любой кодировке так, как будто каждый символ занимает фиксированное число байт, обречены на провал.
Вы мне доказать что-то пытаетесь? Я представляю, как работает юникод.

Десятки байт на code unit в таком случае будет возникать в довольно экзотических конфигурациях. Не то, чтобы такая конфигурация невозможна. Маловероятно, как раз то, что потребуется «быстро» «менять местами» такие code unit'ы.

Для подавляющего большинства операций хватит «запакованных» строк. А если вам требуется «ускорение», то и десять байт на графему будет не жалко.

>>многие валидные тексты в неанглийских языках отсекутся.

Не отсекутся, если в конструкторе спрашивать «Сколько надо байтов на юнит?».

Кстати, а в каких языках будет много кодпойнтов на юнит? Хангыль?

>>Да, можно, но этим вы не добьётесь того, что любому символу (в смысле графемному кластеру) соответствует фиксированное количество байт.

Любую строку можно разложить на фиксированные блоки. Однако, я понимаю, что вы имеете в виду. Можно, вероятно, будет придумать такую строку, которая будет всё портить. (Ну, типа, квадратичный прирост по памяти.) Но ещё раз, для таких случаев должны работать «упакованные» строки.
Кстати, а в каких языках будет много кодпойнтов на юнит?

Это некорректный вопрос, на code unit не может быть много code point'ов. Наоборот — может быть. Я больше имел в виду тексты с диакритикой.

Да, для подавляющего большинства операций хватит UTF-8 строк. А всякие адаптивные вещи — это настолько экзотика, что я даже не знаю, где это может пригодиться.

Можно, вероятно, будет придумать такую строку, которая будет всё портить.

Да, примерно это я и имел в виду. Любую строку можно разбить на графемные кластеры, взять длину максимального из них за длину code unit'а, и перекодировать строку так, чтобы каждый кластер занимал фиксированное (не больше максимального) число байт. Но, как мне кажется, это настолько специфический случай, что я даже не могу представить, где это может понадобиться.

В целом же у нас с вами, как я вижу, консенсус)

что значит "безногие"?

спасибо

Это жаргон. Это такие символы, которые я привёл в завершение своего поста. Обычные буквы, с навешанным на них громадным количеством "комбинируемой" диакритики. Юникод это позволяет, кажется, без ограничений, и есть несколько популярных скриптов, которые это демонстрируют. https://zalgo.org/

"Безногими" они называются не из-за формы символов, а из-за аналогии с популярным пранком, для придания зловещего вида тексту. http://nouveau.lurkmore.net/%D0%91%D0%95%D0%97%D0%9D%D0%9E%D0%93N%D0%9C

У вас шестеренка на рисунке какая-то неправильная. Стороны зубцов не должны быть прямыми, и дырка в середине, кажется, крупновата.
Sign up to leave a comment.