Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Юникод и .NET

Anna | 17.06.2014 | нет комментариев
От переводчика. На Прогре теснее многократно публиковались статьи как по Юникоду, так и по строкам в .NET. Впрочем статьи о Юникоде применительно к .NET ещё не было, следственно я решил перевести статью общепринятого гуру .NET Джона Скита. Она закрывает обещанный мной цикл из трёх статей-переводов Дж. Скита, посвящённых строкам в .NET. Как неизменно, буду рад примечаниям и исправлениям.
Логотип Юникода

Вступление

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

Источники

Нижеприведенные ссылки как минимум пригодны настоль же, насколько и данная статья, а может — и больше пригодны. Я сам применял их при написании данной статьи. В них много пригодных и добротных материалов, и если в этой статье вы подметите какие-нибудь неточности, то данные источники обязаны быть больше точными.

Двоичные и текстовые данные – это две различные вещи

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

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

Символьные (текстовые) данные являются последовательностью символов.

Глоссарий Юникода определяет символ как:

  1. Минимальный компонент письменного языка, содержащий семантическое значение; указывает на отвлеченный толк и/или форму, в различие от особых форм (таких как глифы); в кодовых таблицах некоторые формы визуального представления символов имеют огромное значение для их понимания читателем.
  2. Синоним абстрактного символа (см. Definition D3 в Section 3.3, Characters and Coded Representations).
  3. Базовая единица кодирования в системе кодировки Юникода.
  4. Английское название для идеографических письменных элементов китайского происхождения.

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

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

Так для чего же Юникод?

Консорциум Юникода пытается стандартизировать обработку символьных данных, включая реформирования из двоичной формы в текстовую и напротив (что именуется декодированием и кодированием соответственно). Помимо того, существует комплект эталонов ISO (10646 в разных версиях), которые делают то же самое; Юникод и ISO 10646 могут рассматриваться как одно и то же, так как они примерно всецело совместимы. (В теории, ISO 10646 определяет больше широкий возможный комплект символов, но это вряд ли когда-нибудь станет задачей.) Множество современных языков и платформ программирования, включая .NET и Java, применяют Юникод для представления символов.

Юникод определяет, среди прочего:

  • репертуар абстрактных символов (abstract character repertoire) — комплект всех символов, которые поддерживаются Юникодом;
  • комплект кодов символов (coded character set) — содержит привязку всякого символа из репертуара к целому неотрицательному числу, называемому кодовой точкой (code point);
  • некоторые формы кодировки символов (character encoding forms) — определяют соответствия между кодовыми точками и последовательностями «кодовых единиц» (просто говоря — соответствия между кодовой точкой, выраженной одним целым числом всякий длины, и группой байт, кодирующей это число);
  • некоторые схемы кодировки символов (character encoding schemes) — определяют соответствия между комплектами кодовых единиц и сериализованными последовательностями байтов.

Отличие между формой кодировки символов и схемой кодировки символов достаточно тонкое, тем не менее, оно рассматривает порядок байтов (endianness). (К примеру, в кодировке UCS-2 последовательность кодовых единиц 0xC2 0xA9 может быть сериализована как 0xC2 0xA9 либо как 0xA9 0xC2 — это решает именно схема кодировки символов.)

Репертуар абстрактных символов Юникода может содержать, в теории, вплотную до 1114112 символов, правда многие теснее зарезервированы как непригодные, а оставшиеся, скорее каждого, никогда не будут назначены. Всякий символ кодируется целым неотрицательным числом от 0 до 1114111 (0x10FFFF). К примеру, заглавная А закодирована десятичным числом 65. Ещё несколько лет назад считалось, что все символы «влезут» в диапазон между 0 и 216-1, а это значило, что всякий символ дозволено представить при помощи 2-х байтов. К сожалению, со временем понадобилось огромнее символов, что привело к происхождению т.н. «суррогатных пар» (surrogate pair). С ними всё стало гораздо труднее (по крайней мере, для меня), а потому огромная часть данной статьи их касаться не будет — я коротко их опишу в разделе «Трудные моменты».

Так что же предоставляет .NET?

Не беспокойтесь, если всё вышесказанное выглядит необычно. О отличиях, описанных выше, следует знать, но на самом деле они не Зачастую выходят на 1-й план. Множество ваших задач, скорее каждого, будет «вертеться» вокруг конвертации некоторого комплекта байтов в некоторый текст и напротив. В таких обстановках вы будете трудиться со конструкцией System.Char (в C# вестима под псевдонимом char), классом System.String (string в C#), а также с классом System.Text.Encoding.

Конструкция Char является самым базовым типом символа в C#, один экземпляр Char представляет один символ Юникода и занимает 2 байта памяти, а значит, может принимать всякое значение из диапазона 0-65535. Имейте в виду, что не все числа из этого диапазона являются валидными символами Юникода.

Класс String в своей основе является последовательностью символов. Он неизменяем (immutable), что значит, что позже создания экземпляра строки вы теснее не можете его (экземпляр) изменить, — разные способы класса String, правда и выглядят так, словно изменяют его содержимое, на самом деле создают и возвращают новую строку.

Класс System.Text.Encoding предоставляет средства конвертации массива байт в массив символов либо в строку, а также напротив. Данный класс является абстрактным; его разные реализации как представлены в .NET’е, так и могут быть написаны самими пользователями. (Задача по созданию имплементацииSystem.Text.Encoding появляется достаточно редко — в большинстве случаев вам хватит тех классов, которые идут в составе .NET’а.) Encoding разрешает отдельно указать кодеры и декодеры, которые обрабатывают состояние между вызовами. Это нужно для многобайтовых схем кодировок символов, когда немыслимо правильно декодировать в символы все байты, полученные из потока. Скажем, если декодер UTF-8 получает на вход два байта 0×41 0xC2, он может возвратить только 1-й символ (заглавную литеру «А»), впрочем для определения 2-й литеры ему необходим 3-й байт.

Встроенные схемы кодировок

Библиотека классов .NET содержит разные схемы кодировок. Ниже приведено изложение этих схем и методы их применения.

ASCII

ASCII является одной из особенно распространённых и единовременно одной из особенно недопонимаемых кодировок символов. Против знаменитому заблуждению, ASCII является 7-битной кодировкой, а не 8-битной: символов с кодами (кодовыми точками) огромнее числа 127 не существует. Если кто-либо извещает, что он использует, к примеру, код «ASCII 154», то дозволено считать, что данный кто-то вообще не понимает, что он делает и говорит. Правда, в качестве отговорки он может заявить что-то о «расширенной ASCII» (extended ASCII). Так вот — нет никакой схемы под наименованием «расширенная ASCII». Есть уйма 8-битных схем кодировок, которые являются надмножеством для ASCII, и для их обозначения изредка применяют термин «расширенной ASCII», что не вовсе правильно. Кодовая точка всякого ASCII-символа совпадает с кодовой точкой аналогичного символа в Юникоде: иными словами, ASCII-символ латинской литеры «x» в нижнем регистре и Юникодный символ этой же самой литеры обозначаются идентичным числом — 120 (0х78 в шестнадцатеричном представлении). .NET-класс ASCIIEncoding (экземпляр которого легко может быть получен через качество Encoding.ASCII), на мой взор, является немножко необычным, так как, кажется, он исполняет кодирование путём простого отбрасывания всех битов позже базовых 7-ми. Это значит, что, к примеру, юникодный символ 0xB5 (знак «микро» — µ) позже кодирования в ASCII и декодирования назад, в Юникод, превратится в символ 0×35 (цифра «5»). (Взамен этого я бы предпочёл, Дабы выводился какой-то особый символ, показывающий, что начальный символ отсутствовал в ASCII и был потерян.)

UTF-8

UTF-8 является отличным и распространённым методом представления символов Юникода. Всякий символ кодируется последовательностью байтов в число от одного до четырёх включительно. (Все символы с кодовыми точками поменьше 65536 кодируются одним, двумя либо тремя байтами; я не проверял, как .NET кодирует суррогатные пары: двумя последовательностями из 1-3 байтов либо одной последовательностью из 4-х байтов.) UTF-8 может отображать все существующие в Юникоде символы и совместим с ASCII таким образом, что любая последовательность ASCII-символов будет перекодирована в UTF-8 без изменений (т.е. последовательность байтов, представляющая символы в ASCII, и последовательность байтов, представляющая те же символы в UTF-8, идентичны). Больше того, первого байта, кодирующего символ, хватит, Дабы определить, сколько ещё байт кодируют данный же символ, если такие вообще есть. UTF-8 сама по себе не требует метку порядка байтов (Byte order mark — BOM), правда она может применяться как метод индикации того, что текст представлен в формате UTF-8. Текст UTF-8, содержащий BOM, неизменно начинается с последовательности трёх байтов 0xEF 0xBB 0xBF. Дабы закодировать строку в UTF-8 в .NET, легко используйте качество Encoding.UTF8. Вообще-то, в большинстве случаев вам не придётся делать даже это — много классов (включая StreamWriter) применяют UTF-8 по умолчанию, когда очевидно не задана никакая иная кодировка. (Не заблуждайтесь, Encoding.Default сюда не относится, это вовсе другое.) Тем не менее, я советую неизменно очевидно указывать кодировку в вашем коде, правда бы ради удобочитаемости и понимания.

UTF-16 и UCS-2

UTF-16 — это как раз та кодировка, в которой .NET работает с символами. Всякий символ представлен последовательностью из 2-х байт; соответственно суррогатная пара занимает 4 байта. Вероятность применения суррогатных пар — это исключительное отличие между UTF-16 и UCS-2: UCS-2 (также знаменит легко как «Юникод») не допускает суррогатные пары и может представлять символы в диапазоне 0-65535 (0-0xFFFF). UTF-16 может иметь различный порядок байтов (Endianness): он может быть от старшего к младшему (big-endian), от младшего к старшему (little-endian), либо же быть машинно-зависимым с опциональным BOM (0xFF 0xFE для little-endian, 0xFE 0xFF для big-endian). В самом .NET, насколько я знаю, на загвоздку суррогатных пар «забили», и всякий символ в суррогатной паре рассматривается как независимый символ, что приводит оригинальной «уравниловке» между UCS-2 и UTF-16. (Точное отличие между UCS-2 и UTF-16 заключается в гораздо больше глубоком понимании суррогатных пар, и я не компетентен в этом аспекте.) UTF-16 в представлении big-endian может быть получена представлен пример кода, конвертирующего файл с UTF-8 в UCS-2:

using System;
using System.IO;
using System.Text;

public class FileConverter
 {
     const int BufferSize = 8096;

     public static void Main(string[] args)
     {
         if (args.Length != 2)
         {
             Console.WriteLine 
                 ("Usage: FileConverter <input file> <output file>");
             return;
         }
         String inputFile = args[0];
         String outputFile = args[1];
         // Открыть TextReader для чтения присутствующего входного файла
         using (TextReader input = new StreamReader 
                (new FileStream (inputFile, FileMode.Open),
                 Encoding.UTF8))
         {
             // Открыть TextWriter для создания и записи в новейший выходной файл
             using (TextWriter output = new StreamWriter 
                    (new FileStream (outputFile, FileMode.Create),
                     Encoding.Unicode))
             {
                 // Сделать буфер
                 char[] buffer = new char[BufferSize];
                 int len;

                 // Копировать данные долями до достижения конца
                 while ( (len = input.Read (buffer, 0, BufferSize)) > 0)
                 {
                     output.Write (buffer, 0, len);
                 }
             }
         }
     }
 }

Подметьте, что в данном коде использованы конструкторы TextReader и TextWriter, которые принимают потоки. Существуют и другие перегрузки конструкторов, принимающие на вход пути к файлам, так что вам не необходимо вручную открывать FileStream; я это сделал лишь в качестве примера. Есть и другие перегрузки конструкторов, принимающие также размер буфера и надобность определения BOM, — в всеобщем, взгляните документацию. И наконец, если вы используете .NET 2.0 и выше, не помешает взглянуть на статический классSystem.IO.File, также содержащий уйма комфортных способов, дозволяющих трудиться с кодировками.

Трудные моменты


Хорошо, это были лишь основы Юникода. Есть уйма других нюансов, на некоторые из которых я теснее намекнул, и я считаю, что людям следует о них знать, даже если они предполагают, что сходственное никогда с ними не произойдёт. Я не предлагаю каких-либо всеобщих методик либо управляющих тезисов — я легко пытаюсь поднять вашу осведомленность в допустимых задачах. Ниже приведён список, и он ни скольким образом не доскональный. Значимо, Дабы вы осознали, что множество описанных задач и сложностей ни в коей мере не являются виной либо ошибками Консорциума Юникода; так же, как и в случае даты, времени и всякий из задач интернационализации, это — «заслуга» Общества, которое с течением времени само сотворило многие твердо трудные задачи.

Культуро-зависимый поиск, сортировка и проч.


Эти задачи описаны в моей статье, посвящённой строкам в .NET (оригиналперевод).

Суррогатные пары

Сейчас, когда Юникод содержит огромнее, чем 65536 символов, он не может вместить их все в 2 байта. Это значит, что один экземпляр конструкции Char не может принимать все допустимые символы. UTF-16 (и .NET) решает эту задачу путём применения суррогатных пар (surrogate pair) — это два 16-битных значения, где всякое значение лежит в диапазоне от 0xD800 и до 0xDFFF. Другими словами, два «типа символа» образуют один «подлинный» символ. (UCS-4 и UTF-32 всецело решают эту загвоздку тем, что у них доступен больше широкий диапазон значений: всякий символ занимает 4 байта, и этого хватает каждому и всякому.) Суррогатные пары — это головная боль, чай это значит, что строка, которая состоит из 10 символов, на самом деле может содержать от 10 до 5 включительно «настоящих» символов Юникода. К счастью, множество приложений не применяют научные либо математические нотации и символы Хан, а следственно вам не необходимо об этом особенно беспокоиться.

Модифицирующие символы

Пример модификации символа
Не все символы из Юникода в итоге итога на экран либо бумагу предстают в виде значка/картинки. Подчёркнутый (акцентированный) символ может быть представлен в качестве 2-х других символов: обыкновенного, неподчёркнутого символа и дальнейшего за ним символа подчёркивания, тот, что называется модифицирующим (либо комбинируемым) символом (Combining character). Некоторые графические интерфейсы поддерживают модифицирующие символы, некоторые нет, и работа вашего приложения будет зависеть от того, какое предположение вы сделаете.

Нормализация

Отчасти из-за таких пророческой, как модифицирующие символы, может быть несколько методов представления того, что в некотором смысле является одним символом. Литера «т быть пригодна при работе со специфичными символами Юникода. Если же вы не можете правильно логировать шестнадцатеричные коды даже простого ASCII-текста — у вас крупные задачи.

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

Позже того, как проблемная строка залогирована, нужно убедиться, является ли она такой, какой должна быть, либо нет. В этом вам поможет веб-страница Unicode code charts. Вы можете предпочесть как комплект символов, в котором уверены, так и искать символы в алфавитном порядке. Удостоверитесь, что всякий символ в строке имеет положительное значение. Как только вы найдёте то место в вашем приложении, где поток символьных данных поврежден, исследуйте это место, узнаете причину ошибки и поправьте её. Поправив все ошибки, удостоверитесь, что приложение работает правильно.

Завершение

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

Источники

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB