Здравствуйте!
Есть такая конструкция, которая выведет первую букву переменной $str, которая будет равна 't'
mb_internal_encoding("UTF-8");
$str = 'test';
print_r($str[0]); // выведет 't'
Но почему это не работает с русскими словами?
mb_internal_encoding("UTF-8");
$str = 'тест';
print_r($str[0]); // выведет '�', вместо 'т'
Кодировка вроде выставлена, но почему в выводе знак вопроса?
Подскажите, пожалуйста, как решить?
Спасибо!
Ответ
Всё дело в том, что в кодировке UTF-8 единичный символ может занимать разное количество байт (от 1 до 6 или даже больше).
Английские буквы, цифры, некоторые знаки препинания занимают по одному байту, и их представление ничем не отличается, как если бы они были закодированы в ASCII. Для букв русского алфавита, букв европейских языков, которых нет английском (например, немецкая Ö), для арабского алфавита, нужно уже по два байта. Распространённые восточные иероглифы занимают уже по 3 байта каждый. А всеми любимые эмодзи кодируются последовательностями от 4 байтов.
Итак, давайте рассмотрим байтовое представление ваших строк.
test -> 74 65 73 74
тест -> D1 82 D0 B5 D1 81 D1 82
Слева показано то, как эти буквы видим мы, справа — их побайтовое представление в памяти компьютера, причём значения байтов записаны в 16-ричной системе счисления (чтобы не занять много места). Как видите, русское слово занимает в два раза больше памяти, хотя букв в нём столько же. Кроме того, обратите внимание, каждый байт в его кодированном варианте не меньше 8016
Теперь вернёмся к PHP. Строки в этом языке о кодировках ничего не знают, а просто хранят в себе только байты. И оператор индексирования тоже ничего про кодировки, буквы, символы не знает; он просто, будучи применённым к строке, возвращает байт с указанным номером. Поэтому, когда вы пытаетесь напечатать первый байт (с номером 0), в первом случае выводится байт со значением 7416, который на экране компьютера виден как маленькая латинская буква «t». А во втором случае на вывод подаётся байт со значением D116, представляющий собой огрызок русской буквы «т»; не зная, какой байт идёт дальше, устройство вывода (терминал или веб-обозреватель) просто рисует знак вопроса.
Что же делать, как напечатать только первый символ из строки? В качестве решения можно применить функции из расширения php-mbstring
$letter = mb_substr($str, 0, 1); // вернёт первый code point из строки
Но учтите, функция mb_substr оперирует понятием code point, а не graphical cluster. Какое же это имеет значение? Стандарт юникода позволяет составлять некоторые буквы из нескольких code points. К примеру, буква «ё» может оказаться комбинацией буквы «е» и умляута.
Комментариев нет:
Отправить комментарий