Что абсолютно необходимо знать каждому программисту о кодировках и наборах символов для работы с текстом

Если вы имеете дело с текстом на компьютере, вам нужно знать о кодировках. Период. Да, даже если вы просто отправляете электронные письма. Даже если вы просто получаете электронные письма. Вам не нужно разбираться во всех деталях, но вы должны хотя бы знать, о чем идет речь во всем этом «кодировании». И сначала хорошие новости: хотя тема может стать запутанной и запутанной, основная идея действительно очень проста.

Эта статья о кодировках и наборах символов . Статья Джоэла Спольски, озаглавленная «Абсолютный минимум», каждый разработчик программного обеспечения должен знать о Unicode и наборах символов (без оправданий!), Является хорошим введением в эту тему, и мне очень нравится читать ее время от времени. Я не решаюсь направлять к нему людей, у которых есть проблемы с пониманием проблем с кодированием, хотя, хотя он и интересен, он довольно светлый по актуальным техническим деталям. Я надеюсь, что эта статья может пролить больше света на то, что такое кодировка и почему весь ваш текст ошибается, когда он вам меньше всего нужен. Эта статья предназначена для разработчиков (с акцентом на PHP), но любой пользователь компьютера сможет извлечь из нее пользу.

Изучение основ

осознавая это на каком-то уровне, но почему-то кажется, что это знание внезапно исчезает при обсуждении текста, поэтому давайте сначала выясним это: компьютер не может хранить «буквы», «числа», «картинки» или что-то еще. Единственное, что он может хранить и с чем он может работать, — это биты . Бит может иметь только два значения: yes или no , true или false , 1 или 0 или как угодно еще, что вы хотите назвать этими двумя значениями. Поскольку компьютер работает с электричеством, «фактический» бит - это вспышка электричества, которая либо есть, либо нет. Для людей это обычно представляется с помощью 1 и 0 , и я буду придерживаться этого соглашения на протяжении всей статьи.

Чтобы использовать биты для представления чего-либо, кроме битов, нам нужны правила. Нам нужно преобразовать последовательность битов во что-то вроде букв, цифр и изображений, используя схему кодирования или для краткости кодировку . Вот так:

  01100010 01101001 01110100 01110011b его  

В этой кодировке , 01100010 обозначает букву «b», 01101001 обозначает букву «i», 01110100 обозначает «t» и 01110011 для «s». Определенная последовательность бит обозначает букву, а буква обозначает определенную последовательность бит. Если вы можете держать это в голове для 26 букв или действительно быстро просматривать информацию в таблице, вы можете читать биты, как книгу.

Вышеупомянутая схема кодирования является ASCII. Строка из 1 и 0 разбита на части по восемь бит каждая (для краткости байт ). Кодировка ASCII определяет таблицу, переводящую байты в понятные человеку буквы. Вот краткая выдержка из этой таблицы:

биты символ
01000001 A
01000010 B
01000011 C
01000100 D
01000101 E
01000110 F

В таблице ASCII указано 95 удобочитаемых символов, включая буквы от A до Z в верхнем и нижнем регистре, числа от 0 до 9, несколько знаков препинания и символы, такие как символ доллара, амперсанд и некоторые другие. . Он также включает 33 значения для таких вещей, как пробел, перевод строки, табуляция, возврат и т. Д. Они не для печати сами по себе, но все же видимы в той или иной форме и полезны напрямую людям. Ряд значений полезен только для компьютера, например коды для обозначения начала или конца текста. Всего в кодировке ASCII определено 128 символов, что является хорошим круглым числом (для людей, имеющих дело с компьютерами), поскольку оно использует все возможные комбинации из 7 бит ( 0000000 , 0000001 , 0000010 до 1111111).1

И вот он, способ представления текста, удобочитаемого человеком, с использованием только 1 и 0 .

  01001000 01100101 01101100 01101100 01101111 00100000 01010111 01101111 01110010 01101100 01100100  

«Hello World»

Важные термины

Чтобы закодировать что-нибудь в ASCII, следуйте таблице справа налево, заменяя биты буквами. Чтобы декодировать строку бит в символы, удобочитаемые человеком, следуйте таблице слева направо, заменяя буквы битами.

encode | enˈkōd |
глагол [с obj. ]
преобразовать в закодированную форму

code | kōd |
существительное
система слов, букв, цифр или других символов, заменяющая другие слова, буквы и т. д.

На encode означает использовать что-то для представления чего-то другого. Кодировка — это набор правил для преобразования чего-либо из одного представления в другое.

Другие термины, которые заслуживают пояснения в этом контексте:

набор символов, кодировка
Набор символов, которые можно кодировать. «Кодировка ASCII включает набор символов из 128 символов». По сути, синоним «кодировки».
code page
«страница» кодов, которые отображают символ в число или последовательность битов. A.k.a. «стол». По сути, синоним «кодирования».
string
Строка — это набор элементов, соединенных вместе. Битовая строка — это набор битов, например 01010011 . Символьная строка — это набор символов, как это . Синоним «последовательности».

Двоичный, восьмеричный, десятичный, шестнадцатеричный

Есть много способов записывать числа. 10011111 в двоичном формате — 237 в восьмеричном — 159 в десятичном — 9F в шестнадцатеричном. Все они представляют одно и то же значение, но шестнадцатеричное число короче и его легче читать, чем двоичное. В этой статье я буду придерживаться двоичного кода, чтобы лучше понять суть и избавить читателя от одного уровня абстракции. Не пугайтесь, увидев коды символов, упоминаемые в других обозначениях в другом месте, это одно и то же.

Excusez-moi?

Теперь, когда мы знаем, что мы ‘ Скажем так: 95 символов на самом деле не так много, когда дело касается языков. Он охватывает основы английского языка, но как насчет того, чтобы написать рискованное письмо по-французски? Straßenübergangsänderungsgesetz на немецком языке? Приглашение на smörgåsbord на шведском языке? Ну, ты не мог. Не в ASCII. Нет спецификации, как представлять любую из букв é, ß, ü, ä, ö или å в ASCII, поэтому вы не можете их использовать.

«Но посмотрите на это», Европейцы заявили: «В обычном компьютере с 8 битами в байтах ASCII тратит впустую весь бит, который всегда установлен на 0 ! Мы можем использовать этот бит, чтобы сжать целые 128 значений. в этот стол! » Так они и сделали. Но даже в этом случае существует более 128 способов обводки, разрезания, косой черты и точки над гласной. Не все варианты букв и закорючек, используемые во всех европейских языках, могут быть представлены в одной таблице максимум с 256 значениями. Итак, в конечном итоге мир получил множество схем кодирования, стандартов, стандартов де-факто и полустандартов, которые охватывают разные подмножества символов. Кому-то нужно было написать документ о шведском на чешском, но он обнаружил, что ни одна кодировка не охватывает оба языка, и изобрел один. По крайней мере, я думаю, это повторялось бесчисленное количество раз.

И не забывать о русском, хинди, арабском, иврите, корейском и всех других языках, активно используемых в настоящее время на этой планете. Не говоря уже о тех, которые больше не используются. После того, как вы решите проблему написания смешанных документов на всех этих языках, попробуйте себя на китайском. Или японец. Оба содержат десятки тысяч символов. У вас есть 256 возможных значений байта, состоящего из 8 бит. Вперед!

Многобайтовые кодировки

Чтобы создать таблицу, которая отображает символы в буквы для языка, который использует более 256 символов, одного байта просто недостаточно . Используя два байта (16 бит), можно закодировать 65 536 различных значений. BIG-5 — это такая двухбайтовая кодировка . Вместо того, чтобы разбивать строку битов на блоки по восемь, он разбивает ее на блоки по 16 и имеет большую (я имею в виду, БОЛЬШУЮ) таблицу, в которой указывается, какой символ соответствует каждой комбинации бит.. BIG-5 в своей базовой форме охватывает в основном иероглифы традиционного китайского языка. GB18030 — это еще одна кодировка, которая по сути делает то же самое, но включает как традиционные, так и упрощенные китайские символы. И прежде чем вы спросите, да, есть кодировки, которые охватывают только упрощенный китайский. Разве сейчас нельзя использовать только одну кодировку?

Вот небольшой отрывок из таблицы GB18030:

биты character
10000001 01000000
10000001 01000001
10000001 01000010
10000001 01000011
10000001 01000100

GB18030 охватывает широкий диапазон символов (включая большая часть латинских символов), но, в конце концов, это еще один специализированный формат кодирования среди многих.

Unicode до замешательства

Наконец-то кому-то надоело, и он решил выковать кольцо, чтобы связать их всех создать один стандарт кодирования для унификации всех стандартов кодирования. Это стандарт Unicode. По сути, он определяет огромную таблицу из 1114 112 кодовых точек, которые можно использовать для всех видов букв и символов. Этого достаточно для кодирования всех европейских, ближневосточных, дальневосточных, южных, северных, западных, доисторических и будущих персонажей, о которых знает человечество. 2 Используя Unicode, вы можете написать документ, содержащий практически на любом языке, используя любые символы, которые вы можете ввести в компьютер. До появления Unicode это было либо невозможно, либо очень-очень сложно. Есть даже неофициальный раздел для клингонов в Unicode. В самом деле, Unicode достаточно велик, чтобы учесть неофициальные области частного использования.

Итак, сколько битов Unicode использует для кодирования всех этих символов? Нет. Поскольку Юникод не является кодировкой.

Запутались? Кажется, многие люди. Unicode в первую очередь определяет таблицу кодовых точек для символов. Это причудливый способ сказать «65 означает A, 66 означает B и 9,731 означает» (серьезно, это так). Как эти кодовые точки на самом деле кодируются в биты — это отдельная тема. Для представления 1,114,112 различных значений двух байтов недостаточно. Три байта — это нормально, но с тремя часто неудобно работать, поэтому четыре байта были бы удобным минимумом. Но, если вы на самом деле не используете китайский или некоторые другие символы с большими числами, для кодирования которых требуется много бит, вы никогда не собираетесь использовать огромный кусок этих четырех байтов.. Если буква «A» всегда кодировалась в 00000000 00000000 00000000 01000001 , «B» всегда в 00000000 00000000 00000000 01000010 и т. Д., Любой документ раздувается в четыре раза больше необходимого.

Для оптимизации существует несколько способов кодирования кодовых точек Unicode в биты. UTF-32 — это такая кодировка, которая кодирует все кодовые точки Unicode с использованием 32 бит. То есть четыре байта на символ. Это очень просто, но часто занимает много места. UTF-16 и UTF-8 — это кодировки переменной длины . Если символ может быть представлен с использованием одного байта (поскольку его кодовая точка — очень маленькое число), UTF-8 закодирует его с помощью одного байта. Если требуется два байта, он будет использовать два байта и так далее. В нем есть продуманные способы использования старших битов в байте для обозначения количества байтов, из которых состоит символ. Это может сэкономить место, но также может привести к потере места, если эти биты сигнала нужно часто использовать. UTF-16 находится посередине, используя как минимум два байта, при необходимости увеличивая до четырех байтов.

character кодировка bits
A UTF-8 01000001
A UTF-16 00000000 01000001
A UTF-32 00000000 00000000 00000000 01000001
UTF-8 11100011 10000001 10000010
UTF-16 00110000 01000010
UTF-32 00000000 00000000 00110000 01000010

Вот и все. Юникод — это большая таблица, отображающая символы в числа, а различные кодировки UTF определяют, как эти числа кодируются как биты. В целом Юникод — это еще одна схема кодирования . В этом нет ничего особенного, он просто пытается охватить все, оставаясь при этом эффективным. И это хорошо. ™

Кодовые точки

Символы обозначаются их «кодовой точкой Unicode». Кодовые точки Unicode записываются в шестнадцатеричном формате (чтобы числа были короче), перед ними ставится «U +» (это именно то, что они делают, это не имеет другого значения, кроме «это кодовая точка Unicode»). Символ Ḁ имеет кодовую точку Unicode U + 1E00. Другими (десятичными) словами, это 7680-й символ таблицы Unicode. Официально он называется «ЛАТИНСКАЯ ЗАГЛАВНАЯ БУКВА А С КОЛЬЦОМ НИЖЕ».

TL; DR

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

биты кодировка символов
11000100 01000010 Windows Latin 1 ÄB
11000100 01000010 Mac Roman ƒB
11000100 01000010 GB18030
символы кодировка bits
Føö Windows Latin 1 01000110 11111000 11110110
Føö Mac Roman 01000110 10111111 10011010
Føö UTF-8 01000110 11000011 10111000 11000011 10110110

Заблуждение Позиции, путаница и проблемы

Сказав все это, мы подходим к реальным проблемам, с которыми ежедневно сталкиваются многие пользователи и программисты, как эти проблемы связаны со всем вышеперечисленным и каково их решение. Самая большая проблема заключается в следующем:

Почему, во имя бога, мои символы искажены ?!

  ÉGÉìÉRÅ [ÉfÉBÉìÉOÇÕìÔÇµÇ ≠ Ç »Ç ¢  

Если вы открываете документ, и он выглядит так, есть одна и только одна причина: ваш текстовый редактор, браузер, текстовый процессор или что-то еще, что пытается прочитать документ, предполагает неправильная кодировка. Это все. Документ не поврежден (ну, если это не так, см. Ниже), вам не нужно выполнять никаких магических действий, вам просто нужно выбрать правильную кодировку для отображения документа.

Гипотетический документ выше содержит следующую последовательность битов:

  10000011 01000111 10000011 10010011 10000011 01010010 10000001 0101101110000011 01100110 10000011 01000010 10000011 10010011 10000011 0100111110000010 11001101 10010011 11101111 10000010 10110101 /pre> 

А теперь быстро, что это за кодировка? Если бы ты просто пожал плечами, ты был прав. Кто знает, верно‽

Что ж, попробуем интерпретировать это как ASCII. Хм, большинство этих байтов начинаются с 3 с бита 1 . Если вы правильно помните, ASCII не использует этот бит. Так что это не ASCII. А как насчет UTF-8? Хм, нет, большинство этих последовательностей недействительны для UTF-8. 4 Значит, UTF-8 тоже отсутствует. Попробуем «Mac Roman» (еще одна схема кодировки для них европейцы). Эй, все эти байты действительны в Mac Roman. 10000011 сопоставляется с «É», 01000111 с «G» и так далее. Если вы прочитаете эту битовую последовательность, используя кодировку Mac Roman, результатом будет «ÉGÉìÉRÅ [ÉfÉBÉìÉOÇÕìÔÇµÇ ≠ Ç» Ç ¢ ». Это похоже на действительную строку, не так ли? Да? Может быть? Ну откуда компьютеру знать? Возможно, кто-то хотел написать «ÉGÉìÉRÅ [ÉfÉBÉìÉOÇÕìÔÇµÇ ≠ Ç» Ç ¢ ». Насколько я знаю, это может быть последовательность ДНК. 5 Если у вас нет лучшего предложения, давайте объявим, что это последовательность ДНК, скажем, этот документ был закодирован в Mac Roman, и назовем это днем .

Конечно, это, к сожалению, полная чушь. Правильный ответ состоит в том, что этот текст закодирован в японской кодировке Shift-JIS и должен был читаться как «エ ン コ ー デ ィ ン グ 難 し く な い». Кто бы мог подумать?

Основная причина искаженного текста: кто-то пытается прочитать последовательность байтов, используя неправильную кодировку. Компьютеру всегда нужно сообщать, в какой кодировке находится текст. В противном случае он не может знать. Существуют разные способы, которыми различные типы документов могут указывать, в какой кодировке они находятся, и эти способы следует использовать. Необработанная битовая последовательность всегда является загадочным окном и может означать что угодно.

Большинство браузеров позволяют выбрать другую кодировку в меню «Вид» в пункте меню «Кодировка текста», что заставляет браузер переинтерпретировать текущую страницу, используя выбранную кодировку. Другие программы могут предлагать что-то вроде «Открыть заново с использованием кодировки…» в меню «Файл» или, возможно, параметр «Импорт…», который позволяет пользователю вручную выбрать кодировку.

Мой документ не поддерживает смысл в любой кодировке!

Если последовательность битов не имеет смысла (для человека) в любой кодировке, документ, скорее всего, в какой-то момент был преобразован неправильно. Допустим, мы взяли приведенный выше текст «ÉGÉìÉRÅ [ÉfÉBÉìÉOÇÕìÔÇµÇ ≠ Ç» Ç », потому что не знали ничего лучше, и сохранили его как UTF-8. Текстовый редактор предположил, что он правильно читает текст в кодировке Mac Roman, и теперь вы хотите сохранить этот текст в другой кодировке. В конце концов, все эти символы являются допустимыми символами Unicode. То есть в Unicode есть кодовая точка, которая может представлять «É», другая - «G» и так далее. Таким образом, мы можем с радостью сохранить этот текст как UTF-8:

  11000011 10001001 01000111 11000011 10001001 11000011 10101100 1100001110001001 01010010 11000011 10000101 01011011 11000011 10001001 0110011011000011 10001001 011000000101100001111000011000011000011000011000011000011000011000011000011  10000111 11000011 10010101 11000011 1010110011000011 10010100 11000011 10000111 11000010 10110101 11000011 1000011111100010 10001001 10100000 11000011 10000111 11000010 10111011 110000ÉÉBB 11000010 10100010  

É 8µRìì теперь битовая последовательность, представляющая текст в формате UTf-ìì. ≠ Ç »Ç ¢". Эта битовая последовательность не имеет абсолютно ничего общего с нашим исходным документом. В какой бы кодировке мы его ни пытались открыть, мы никогда не получим из него текст «エ ン コ ー デ ィ ン グ は 難 し く な い». Это полностью потеряно. Было бы возможно восстановить исходный текст из него, если бы мы знали, что документ Shift-JIS был неправильно истолкован как Mac Roman, а затем случайно сохранен как UTF-8 и полностью изменил эту цепочку ошибок. Но это была бы удачная случайность.

Часто определенные битовые последовательности недействительны в определенной кодировке. Если мы попытаемся открыть исходный документ с использованием ASCII, некоторые байты будут действительны в ASCII и сопоставлены с реальным символом, а другие - нет. Программа, с помощью которой вы ее открываете, может принять решение о молчаливом отбрасывании любых байтов, недопустимых для выбранной кодировки, или, возможно, заменить их на ? . Также существует «замещающий символ Unicode» (U + FFFD), который программа может решить вставить для любого символа, который она не может правильно декодировать при попытке обработки Unicode. Если документ сохранен с удалением или заменой некоторых символов, то эти символы действительно исчезли навсегда, без возможности их реконструирования.

Если документ был неправильно истолкован и преобразован в другую кодировку, он сломан. Попытка «починить» его может быть успешной или неудачной, но обычно это не так. Любое ручное переключение битов или другое кодирование вуду - это по большей части вуду. Он пытается исправить симптомы после того, как пациент уже умер.

Итак, как правильно обрабатывать кодировки?

Это действительно просто: Знайте в какой кодировке находится определенный фрагмент текста, то есть определенная последовательность байтов, затем интерпретируйте его с помощью этой кодировки. Это все, что вам нужно сделать. Если вы пишете приложение, которое позволяет пользователю вводить текст, укажите, какую кодировку вы принимаете от пользователя. Для любого текстового поля программист обычно может выбрать его кодировку. Для любого типа файла, который пользователь может загрузить или импортировать в программу, должна быть спецификация, в какой кодировке должен быть этот файл. В качестве альтернативы, пользователю нужен какой-то способ сообщить программе, в какой кодировке находится файл. Эта информация может быть частью самого формата файла, или это может быть выбор, сделанный пользователем (не то, что большинство пользователей обычно знают, если они не читали эту статью).

Если вам нужно преобразовать из одна кодировка в другую, делайте это чисто, используя специальные инструменты. Преобразование между кодировками - утомительная задача сравнения двух кодовых страниц и принятия решения о том, что символ 152 в кодировке A совпадает с символом 4122 в кодировке B, с последующим изменением битов соответствующим образом. Это конкретное колесо не нуждается в изобретении, и любой основной язык программирования включает в себя некоторый способ преобразования текста из одной кодировки в другую без необходимости думать о кодовых точках, страницах или битах вообще.

Скажем, ваше приложение должен принимать файлы, загруженные в GB18030, но внутри вы обрабатываете все данные в UTF-32. Такой инструмент, как iconv , может чисто преобразовать загруженный файл с однострочным текстом, например iconv ('GB18030', 'UTF-32', $ string) . То есть он сохранит символы при изменении базовых битов:

character кодировка GB18030 Кодировка UTF-32
10111111 01101100 00000000 00000000 01111110 00100111

Вот и все. содержимое строки, то есть символы, читаемые человеком, не изменилось, но теперь это допустимая строка UTF-32. Если вы продолжите рассматривать его как UTF-32, проблем с искаженными символами не будет. Однако, как обсуждалось в самом начале, не все схемы кодирования могут представлять все символы. Невозможно закодировать символ «縧» ни в какой схеме кодирования, разработанной для европейских языков. Если вы попытаетесь это сделать, случится что-то Плохое ™.

Unicode полностью

Именно поэтому в наши дни практически нет оправдания, чтобы не использовать Unicode полностью. Некоторые специализированные кодировки могут быть более эффективными, чем кодировки Unicode для определенных языков. Но если вы не храните терабайты и терабайты очень специализированного текста (а это много текста), обычно нет причин для беспокойства по этому поводу. Проблемы, возникающие из-за несовместимых схем кодирования, в наши дни намного хуже, чем потраченный впустую гигабайт или два. И это станет еще более актуальным, поскольку объем хранилища и пропускная способность будут расти и дешевле.

Если ваша система должна работать с другими кодировками, конвертируйте их в Unicode при вводе и конвертируйте обратно в другие кодировки на выходе. как надо. В противном случае помните, с какими кодировками вы имеете дело в какой момент, и конвертируйте их по мере необходимости, если это возможно без потери информации.

Flukes

У меня этот веб-сайт обращается к базе данных. Мое приложение обрабатывает все как UTF-8 и сохраняет это как таковое в базе данных, и все работает нормально, но когда я смотрю на интерфейс администратора базы данных, мой текст искажается. - Обезьяна анонимного кода

Бывают ситуации, когда кодировки обрабатываются неправильно, но все еще работает. Часто встречающаяся ситуация - это база данных, для которой задано значение latin-1 , и приложение, которое работает с UTF-8 (или любой другой кодировкой). Практически любая комбинация значений 1 и 0 допустима в однобайтовой схеме кодирования latin-1 . Если база данных получает текст от приложения, которое выглядит как 11100111 10111000 10100111 , она с радостью сохранит его, думая, что приложение предназначено для хранения трех латинских символов «ç¸§». В конце концов, почему бы и нет? Затем он позже возвращает эту битовую последовательность обратно в приложение, которое с радостью примет ее как последовательность UTF-8 для «», которую оно изначально сохраняло.. Интерфейс администратора базы данных автоматически определяет, что для базы данных задано значение latin-1 , и интерпретирует любой текст как latin-1 , поэтому все значения выглядят только искаженными в интерфейсе администратора.

Это случай дурацкого везения, когда что-то работает, а на самом деле - нет. Любые операции с текстом в базе данных могут работать или не работать должным образом, поскольку база данных неправильно интерпретирует текст. В худшем случае база данных непреднамеренно уничтожает весь текст во время некоторой случайной операции через два года после того, как система была запущена в производство, потому что она работала с текстом в неправильной кодировке. 6

UTF-8 и ASCII

Гениальная особенность UTF-8 заключается в том, что он двоично совместим с ASCII, который де-факто является базовой линией для всех кодировок. Все символы, доступные в кодировке ASCII, занимают только один байт в UTF-8, и это те же байты, что и в ASCII. Другими словами, ASCII отображает 1: 1 в UTF-8. Любой символ, не входящий в ASCII, занимает два или более байта в UTF-8. Для большинства языков программирования, которые ожидают синтаксического анализа ASCII, это означает, что вы можете включать текст UTF-8 непосредственно в свои программы:

  $ string = "漢字";  

Сохранение этого как UTF-8 приводит к следующей битовой последовательности:

  00100100 01110011 01110100 01110010 01101001 01101110 01100111 0010000000111101 00100000 00100010 11100110 10111100 10100010 11101110101 101110101  00111011  

Только байты с 12 по 17 (те, которые начинаются с 1 ) являются символами UTF-8 (два символа по три байта каждый). Все окружающие символы в отличном формате ASCII. Синтаксический анализатор прочитает это следующим образом:

  $ string = "11100110 10111100 10100010 11100101 10101101 10010111";  

К синтаксическому анализатору , все, что следует за кавычками, является просто последовательностью байтов, которая будет восприниматься как есть, пока не встретит другую кавычку. Если вы просто выводите эту последовательность байтов, вы выводите текст UTF-8. Больше ничего делать не нужно. Парсеру не нужно специально поддерживать UTF-8, ему просто нужно буквально воспринимать строки. Наивные парсеры могут поддерживать Unicode таким образом, фактически не поддерживая Unicode. Однако многие современные языки явно поддерживают Unicode.

Кодировки и PHP

PHP не поддерживает Unicode. За исключением того, что он на самом деле неплохо его поддерживает. В предыдущем разделе показано, как символы UTF-8 могут быть встроены в любую программу напрямую без проблем, поскольку UTF-8 обратно совместим с ASCII, а это все, что нужно PHP. Утверждение «PHP изначально не поддерживает Unicode» верно и, похоже, вызывает большую путаницу в сообществе PHP.

Ложные обещания

Одна моя любимая мозоль - это функции utf8_encode и utf8_decode . Я часто вижу бессмыслицу вроде «Чтобы использовать Unicode в PHP, вам нужно utf8_encode ваш текст на входе и utf8_decode на выходе» . Эти две функции, похоже, обещают какое-то автоматическое преобразование текста в UTF-8, что «необходимо», поскольку «PHP не поддерживает Unicode». Если вы вообще читали эту статью, вы должны знать, что

  1. нет ничего особенного в UTF-8 и
  2. вы не может закодировать текст в UTF-8 постфактум

Чтобы прояснить второй момент: весь текст уже закодирован в некоторой кодировке. Когда вы вводите его в исходный код, он имеет некоторую кодировку. В частности, все, что вы сохранили, как в текстовом редакторе. Если вы получили его из базы данных, он уже в некоторой кодировке. Если вы читаете его из файла, он уже в некоторой кодировке.

Текст либо закодирован в UTF-8, либо нет. Если это не так, он закодирован в ASCII, ISO-8859-1, UTF-16 или в другой кодировке. Если он не закодирован в UTF-8, но должен содержать «символы UTF-8», 7 , то у вас когнитивный диссонанс. Если он действительно содержит символы, закодированные в UTF-8, то на самом деле он закодирован в UTF-8. Текст не может содержать символы Юникода без кодирования в одной из кодировок Юникода.

Так что же в мире делает utf8_encode тогда?

"Кодирует строку ISO-8859-1 в UTF-8" 8

Ага! На самом деле автор хотел сказать, что он преобразует кодировку текста из ISO-8859-1 в UTF-8. Это все, что нужно сделать. utf8_encode , должно быть, был назван каким-то европейцем без всякой дальновидности, и это ужасное, ужасное неправильное употребление. То же самое и для utf8_decode . Эти функции бесполезны для каких-либо целей, кроме преобразования между ISO-8859-1 и UTF-8. Если вам нужно преобразовать строку из любой другой кодировки в любую другую кодировку, не ищите ничего, кроме iconv .

utf8_encode это не волшебная палочка, которую нужно размахивать над любым текстом, потому что «PHP не поддерживает Unicode». Скорее, кажется, что это вызывает больше проблем с кодированием, чем решает, благодаря ужасному именованию и ничего не знающим разработчикам.

Нативно-схматичный

Итак, что это значит для языка: изначально поддерживает или не поддерживает Unicode? Это в основном относится к тому, предполагает ли язык, что один символ равен одному байту или нет. Например, PHP разрешает прямой доступ к символам строки с использованием нотации массива:

  echo $ string [0];  

Если бы эта $ string была в однобайтовой кодировке, это дало бы нам первый символ. Но только потому, что «символ» совпадает с «байтом» в однобайтовой кодировке.. PHP просто дает нам первый байт , не думая о «символах». Строки - это байтовые последовательности для PHP, ни больше, ни меньше. Все эти «читаемые символы» - это человеческое дело, и PHP это не волнует.

  01000100 01101111 01101110 00100111 01110100D on 't01100011 01100001 01110010 01100101 00100001c!  

То же самое касается многих стандартных функций, таких как substr , strpos , trim код> и так далее. Неподдержка возникает, если есть несоответствие между длиной байта и символа.

  11100110 10111100 10100010 11100101 10101101 10010111 漢 字  

Использование $ string [0] на указанная выше строка снова даст нам первый байт , который равен 11100110 . Другими словами, треть трехбайтового символа «». 11100110 сам по себе является недопустимой последовательностью UTF-8, поэтому строка теперь разорвана. Если вам так хочется, вы можете попытаться интерпретировать это в какой-либо другой кодировке, где 11100110 представляет допустимый символ, что приведет к некоторому случайному символу. Получайте удовольствие, но не используйте это в продакшене.

И это все, что вам нужно. «PHP изначально не поддерживает Unicode» просто означает, что большинство функций PHP предполагают один байт = один символ , что может привести к тому, что многобайтовые символы будут разрезаны пополам или неправильно вычислена длина строк. если вы наивно используете функции, не поддерживающие многобайтовые значения, для многобайтовых строк. Это не означает, что вы не можете использовать Unicode в PHP или что каждая строка Unicode должна быть освящена utf8_encode или другой подобной ерундой.

К счастью, есть расширение Multibyte String, которое реплицирует все важные строковые функции в многобайтовой манере. Использование mb_substr ($ string, 0, 1, 'UTF-8') в приведенной выше строке правильно возвращает 11100110 10111100 10100010 , что является целым "漢 " характер. Поскольку функциям mb_ теперь приходится думать о том, что они делают, им нужно знать, над какой кодировкой они работают. Поэтому каждая функция mb_ также принимает параметр $ encoding . В качестве альтернативы это можно установить глобально для всех функций mb_ с использованием mb_internal_encoding .

Использование и злоупотребление обработкой кодировок PHP

Вся проблема (не) поддержки Unicode в PHP заключается в том, что это просто не волнует . Строки - это байтовые последовательности для PHP. Какие именно байты не имеют значения. PHP ничего не делает со строками, кроме как хранит их в памяти. PHP просто не имеет понятия ни о символах, ни о кодировках.. И если он не пытается манипулировать строками, ему тоже не нужно; он просто удерживает байты, которые могут или не могут в конечном итоге быть интерпретированы кем-то как символы. Единственное требование PHP к кодировкам - сохранение исходного кода PHP в кодировке, совместимой с ASCII. Парсер PHP ищет определенные символы, которые говорят ему, что делать. $ ( 00100100 ) сигнализирует о начале переменной, = ( 00111101 ) присвоение, " ( 00100010 ) начало и конец строки и т. д. Все остальное, что не имеет особого значения для синтаксического анализатора просто воспринимается как буквальная последовательность байтов. Это включает все, что находится в кавычках, как обсуждалось выше. Это означает следующее:

  1. Вы не можете сохранить исходный код PHP код в несовместимой с ASCII кодировке. Например, в UTF-16 " закодирован как 00000000 00100010 . Для PHP, который пытается читать все как ASCII, это байт NUL , за которым следует ". PHP, вероятно, получит икоту, если каждый второй найденный символ - это NUL байт.

  2. Вы можете сохранить исходный код PHP в любой ASCII-совместимой кодировке. Если первые 128 кодов точки кодирования идентичны ASCII, PHP может анализировать их. Все символы, которые каким-либо образом значимы для PHP, находятся в пределах 128 кодовых точек, определенных ASCII. Если строковые литералы содержат какие-либо другие кодовые точки, PHP не заботится. Вы можете сохраните исходный код PHP в ISO-8859-1, Mac Roman, UTF-8 или в любой другой ASCII-совместимой кодировке. Строковые литералы в вашем скрипте будут иметь любую кодировку, в которой вы сохранили исходный код.

  3. $ foo = file_get_contents ('bar.txt'); re>

    Приведенное выше просто считывает биты из bar.txt в переменную $ foo . PHP не пытается интерпретировать, преобразовывать, кодировать или иным образом возиться с содержимым. Файл может даже содержать двоичные данные, такие как изображение, PHP не заботится.

  4. Если внутренняя и внешняя кодировки должны совпадать, они должны совпадать. Распространенным случаем является локализация, когда исходный код содержит что-то вроде echo localize ('Foobar') , а внешний файл локализации содержит что-то вроде этого:

      msgid "Foobar" msgstr "フ ー バ ー"  

    Обе строки "Foobar" должны иметь идентичное битовое представление, если вы хотите найти правильную локализацию. Если исходный код был сохранен в ASCII, но файл локализации в UTF-16, строки не совпадали. Потребовалось бы какое-то преобразование кодировки или использование функции сопоставления строк с поддержкой кодировки.

Проницательный читатель может спросить здесь, можно ли сохранить, скажем, последовательность байтов UTF-16 внутри строкового литерала файла исходного кода в кодировке ASCII. , на который ответ будет: абсолютно.

  echo "UTF-16";  

Если вы можете привести свой текст редактор для сохранения частей echo " и "; в ASCII и только UTF-16 в UTF-16, это будет работать нормально. Необходимое двоичное представление для этого выглядит так:

  01100101 01100011 01101000 01101111 00100000 00100010e cho "11111110 11111111 00000000 01010101 00000000 01010100 (маркер UTF-16) U T00000000 01000110 00000000 00101101 00000000  00110001F - 100000000 00110110 00100010 001110116 ";  

Первая строка и последние два байта - это ASCII. Остальное - UTF-16 с двумя байтами на символ. Ведущий 11111110 11111111 в строке 2 - это маркер, необходимый в начале текста в кодировке UTF-16 (требуется стандартом UTF-16, PHP наплевать). Этот PHP-скрипт успешно выведет строку «UTF-16», закодированную в UTF-16, потому что он просто выводит байты между двумя двойными кавычками, которые представляют текст «UTF-16», закодированный в UTF-16. Файл с исходным кодом не является ни полностью допустимым ASCII, ни UTF-16, поэтому работать с ним в текстовом редакторе не так уж и весело.

Итог

PHP поддерживает Unicode или, фактически, любую кодировку, и это нормально, пока выполняются определенные требования, чтобы синтаксический анализатор был доволен и программист знал, что он делает. Вам действительно нужно быть осторожным только при манипулировании строками, включая нарезку, обрезку, подсчет и другие операции, которые должны выполняться на уровне символа , а не на уровне байтовый уровень. Если вы ничего не делаете со своими строками, кроме их чтения и вывода, у вас вряд ли возникнут проблемы с поддержкой PHP кодировок, которых у вас не было бы и на любом другом языке.

Языки с поддержкой кодирования

Что же тогда означает для языка поддержка Unicode? Например, Javascript поддерживает Unicode. Фактически, любая строка в Javascript закодирована в UTF-16. Фактически, это единственное, чем занимается Javascript. У вас не может быть строки в Javascript, не закодированной в UTF-16. Javascript поддерживает Unicode до такой степени, что нет возможности иметь дело с любой другой кодировкой в ​​основном языке. Поскольку Javascript чаще всего запускается в браузере, это не проблема, поскольку браузер может обрабатывать рутинную логистику кодирования и декодирования ввода и вывода.

Другие языки - это просто кодирование- в курсе . Внутри они хранят строки в определенной кодировке, часто UTF-16. В свою очередь, им нужно сказать или попытаться определить кодировку всего, что связано с текстом.. Им необходимо знать, в какой кодировке сохранен исходный код, в какой кодировке файла они должны читать, в какой кодировке вы хотите выводить текст; и они конвертируют кодировки на лету по мере необходимости с некоторым проявлением Unicode в качестве посредника. Они делают то же самое, что и вы можете/должны/должны делать в PHP полуавтоматически за кулисами. Это не лучше и не хуже PHP, просто другое. Приятно то, что стандартные языковые функции работают со строками Just Work ™, тогда как в PHP нужно уделять некоторое внимание тому, может ли строка содержать многобайтовые символы или нет, и соответственно выбирать функции обработки строк.

Глубина Unicode

Поскольку Unicode имеет дело с множеством различных сценариев и множеством различных проблем, он имеет большую глубину. Например, стандарт Unicode содержит информацию по такой проблеме, как унификация идеограммы CJK. Это означает информацию о том, что два или более китайских/японских/корейских символа фактически представляют один и тот же символ при немного разных методах написания. Или правила преобразования нижнего регистра в верхний регистр, наоборот, и туда и обратно, что не всегда так просто во всех сценариях, как в большинстве западноевропейских сценариев, производных от латиницы. Некоторые символы также могут быть представлены с использованием разных кодовых точек. Буква «ö», например, может быть представлена ​​с помощью кодовой точки U + 00F6 («СТРОЧНАЯ ЛАТИНСКАЯ БУКВА O С ДИАРЕЗИСОМ») или двух кодовых точек U + 006F («СТРОЧНАЯ ЛАТИНСКАЯ БУКВА O») и U + 0308 (« ОБЪЕДИНЯЯ ДИАРЕЗ »), это буква« о »в сочетании с« ». В UTF-8 это либо двухбайтовая последовательность 11000011 10110110 , либо трехбайтовая последовательность 01101111 11001100 10001000 , которые представляют один и тот же читаемый человеком символ. Таким образом, в стандарте Unicode существуют правила, регулирующие Нормализацию , то есть то, как одна из этих форм может быть преобразована в другую. Это и многое другое выходит за рамки данной статьи, но об этом следует знать.

Final TL; DR

  • Текст всегда последовательность битов, которую необходимо преобразовать в понятный человеку текст с помощью таблиц поиска. Если используется неправильная таблица поиска, используется неправильный символ.
  • На самом деле вы никогда не имеете дела напрямую с «символами» или «текстом», вы всегда имеете дело с биты через несколько уровней абстракции. Неправильные результаты являются признаком отказа одного из уровней абстракции.
  • Если две системы взаимодействуют друг с другом, им всегда нужно указать, в какой кодировке они хотят общаться друг с другом. Простейший пример этого веб-сайта, который сообщает вашему браузеру, что он закодирован в UTF-8.
  • В наши дни стандартной кодировкой является UTF-8, поскольку она может кодировать практически любой интересующий символ, обратно совместима с де-факто базовым ASCII и является относительно экономичной для большинства тем не менее, случаев использования.
    • Иногда все еще используются другие кодировки, но у вас должна быть конкретная причина для решения проблем, связанных с наборами символов, которые могут кодировать только подмножество Unicode.
  • Дни один байт = один символ прошли, и программисты и программы должны наверстать упущенное.

Теперь у вас действительно больше не должно быть оправданий в следующий раз, когда вы искажаете текст.


  1. Да, это означает, что ASCII может храниться и передаваться с использованием только 7 битов, что часто бывает. Нет, это выходит за рамки данной статьи, и в качестве аргумента мы предположим, что старший бит в ASCII «потрачен впустую». ↩

  2. А если нет, то он будет расширен. Это уже было несколько раз. ↩

  3. Обратите внимание, что когда я использую термин «начало» вместе с «байтом», я имею в виду его с точки зрения восприятия человеком. ↩

  4. Внимательно прочтите спецификацию UTF-8, если вы хотите следовать ей ручкой и бумагой. ↩

  5. Эй, я программист, а не биолог. ↩

  6. И, конечно, не будет недавней резервной копии. ↩

  7. «Символ Юникода» - это кодовая точка в таблице Юникода. «あ» не является символом Юникода, это буква хираганы あ. Для него есть кодовая точка Unicode, но это не делает саму букву символом Unicode. «Символ UTF-8» - это оксюморон, но его можно растянуть для обозначения того, что технически называется «последовательностью UTF-8», которая представляет собой последовательность байтов из одного, двух, трех или четырех байтов, представляющих один символ Unicode. Оба термина часто используются в смысле «любая буква, не являющаяся частью моей клавиатуры» , что абсолютно ничего не означает. ↩

  8. http://www.php.net/manual/en/function.utf8-encode.php ↩

Об авторе

Дэвид К. Зентграф - веб-разработчик, частично работающий в Японии и Европе, и постоянный участник Stack Overflow. Если у вас есть отзывы, критика или дополнения, вы можете попробовать @deceze в Твиттере, угадать его адрес электронной почты или найти его, используя проверенные временем методы. Эта статья была опубликована на kunststube.net. И нет, в "Kunststube" нет ругательного слова.

Оцените статью
techsly.ru
Добавить комментарий