Во-первых, байты, входящие в состав многобайтовых символов, тщательно подобраны. Нулевой байт (ASCII 0) никогда не встречается в качестве n-ого байта в последовательности (где n > 1); то же самое справедливо для таких распространенных символов, как косая черта (обычно используется для разделения компонентов пути к файлу). На самом деле никакой байт из диапазона 0x00-0x7F не может быть частью никакого другого символа.
Второй байт многобайтового символа однозначно определяет, сколько байтов за ним следует. Этот второй байт всегда выбирается из диапазона от 0хС0
до 0хF0
, а следующие за ним — из диапазона от 0x80
до 0xBF
. Таким образом, схема кодирования свободна от состояния и позволяет восстанавливать пропущенные или искаженные байты.
UTF-8 — одна из самых распространенных и гибких кодировок в мире. Она применяется с начала 1990-х годов и является кодировкой по умолчанию XML-документов. В этой главе мы будем иметь дело главным образом именно с UTF-8.
4.2. Кодировки в пост-ASCII мире
«Век ASCII» прошел, хотя не все еще осознали этот факт. Многие допущения, которые программисты делали в прошлом, уже несправедливы. Нам необходимо новое мышление.
Есть две идеи, которые, на мой взгляд, являются основополагающими, почти аксиомами. Во-первых, строка не имеет внутренней интерпретации. Она должна интерпретироваться в соответствии с некоторым внешним стандартом. Во-вторых, байт и символ — не одно и то же; символ может состоять из одного или нескольких байтов. Есть и другие уроки, но это самое важное.
Эти факты оказывают на программирование тонкое влияние. Рассмотрим сначала, как следует работать с символьными строками по-современному.
4.2.1. Библиотека jcode и переменная $KCODE
Чтобы использовать в Ruby разные наборы символов, вы должны знать о глобальной переменной $KCODE
, от значения которой зависит поведение многих системных методов, манипулирующих строками. (Кстати говоря, буква K — напоминание о кандзи, одной из иероглифических азбук в японском языке.) Эта переменная принимает одно из пяти стандартных значений, каждое из которых представлено одной буквой, неважно — строчной или прописной (ASCII и NONE — одно и то же).
a ASCII
n NONE (ASCII)
е EUC
s SJIS
u UTF-8
Для ясности можно пользоваться и полными названиями (например, $KCODE='UTF-8'
). Важен только первый символ.
О кодировке ASCII мы уже знаем. EUC и Shift-JIS (SJIS) нам малоинтересны. Мы сосредоточимся на значении UTF-8.
Установив значение $KCODE
, вы задаром получаете весьма богатую функциональность. Например, метод inspect
(он автоматически вызывается при обращении к методу p
для печати объекта в читаемом виде) обычно учитывает текущее значение $KCODE
.
$KCODE = 'n'
# Для справки: французское слово 'épée'
# обозначает разновидность меча (sword).
eacute = ''
eacute << 0303 << 0251 # U+00E9
sword = eacute + 'p' + eacute + 'e'
p eacute # '303251'
p sword # '303251p303251e'
$KCODE = 'u'
p eacute # 'é'
p sword # 'épée'
Регулярные выражения в режиме UTF-8 тоже становятся несколько «умнее».
$KCODE = 'n'
letters = sword.scan(/(.)/)
# [['303'], ['251'], ['p'], ['303'], ['251'], ['e']]
puts letters.size # 6
$KCODE = 'u'
letters = sword.scan(/(.)/)
# [['é'], ['p'], ['é'], ['e']]
puts letters.size # 4
Библиотека jcode
предоставляет также несколько полезных методов, например jlength
и each_char
. Рекомендую включать эту библиотеку с помощью директивы require
всякий раз, как вы работаете с кодировкой UTF-8.
В следующем разделе мы снова рассмотрим некоторые типичные операции со строками и регулярными выражениями. Заодно поближе познакомимся с jcode
.
4.2.2. Возвращаясь к строкам и регулярным выражениям
При работе с UTF-8 некоторые операции ничем не отличаются. Например, конкатенация строк выполняется так же, как и раньше:
'éр' + 'éе' # 'épée'
'éр' << 'éе' # 'épée'
Поскольку UTF-8 не имеет состояния, то для проверки вхождения подстроки тоже ничего специально делать не нужно:
'épée'.include?('é') # true
Однако при написании интернациональной программы некоторые типичные допущения все же придется переосмыслить. Ясно, что символ больше не эквивалентен байту. При подсчете символов или байтов надо думать о том, что именно мы хотим сосчитать и для чего. То же относится к числу итераций.
По общепринятому соглашению, кодовую позицию часто представляют себе как «программистский символ». Это еще одна полуправда, но иногда она оказывается полезной.