Unicode сколько бит на символ

от admin

Общее представление о Unicode, UTF-8, UTF-16 LE/BE, BOM

Unicode(далее юникод) — это стандарт, который определяет таблицу символов и способы машинного представления (кодировки) этих символов в байтах.

Таблица символов ставит каждому символу в соответствие значение в виде U+[число в шестнадцатиричной СС]. Например, U+0053 соответствует заглавной латинской букве S. Таблица unicode

Unicode содержит 1,114,112 символов в диапазоне от 0 до 10FFFF. Все символы разделены на 17 групп от группы 0 до 16.

Существуют следующие байтовые представления символов юникода UTF-8, UTF-16(UTF-16 LE или UTF-16 BE), UTF-32(UTF-32 LE или UTF-32 BE).

Каждая кодировка устанавливает свои правила преобразования символа юникод в байты и использует разное количество байтов для представления разных символов.

Например, кодировка UTF-8 использует от 1 до 4 байтов и позволяет покрыть все 1,114,112 символов юникода (потенциально даже больше символов). Первые 128 символов в таблице юникод совпадают с 128 символами из кодовой таблицы ASCII, и UTF-8 кодирует эти 128 символов так же, как и ASCII. В качестве байтового представления используется просто порядковый номер в таблице.

Ниже кусок юникод-таблицы, первые 128 символов, в ней представлены порядковый номер и символ

чтобы получить порядковый номер(U+xxxx) в таблице нужно к числу строки(0000, 0010, 0020, …, 0070) добавить номер столбца(0, 1, 2, …, F), например ‘K’ — это ‘U+004B’

порядковый номер в общем случае не является машинным представлением, но в случае первых 128 символов совпадает с байтовым кодом по версии ASCII и UTF-8

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

кусок таблицы, всю можно посмотреть тут

Один символ кодировки UTF-16 представлен последовательностью двух байтов или двух пар байтов (т.е. 2 или 4 байта на символ). Получается, наименьшей единицей представления символа является 2 байта (называют “слово”). Который из двух байтов в слове идёт впереди, старший или младший, зависит от порядка байтов. Суффиксы BE и LE в названии кодировки указывают порядок. BE — big-endian — от старшего к младшему; LE — little-endian — от младшего к старшему.

Чтобы определить порядок байтов на основании самого файла, используется специальный юникод-символ U+FEFF, называемый BOM (Byte Order Mark), меткой последовательности байтов. Символ U+FEFF добавляется в начало текстового файла, считав который можно определить кодировку файла.

Как видно из таблицы

  • UTF-8 c BOM записывает в начало файла метку U+FEFF в виде 3-х байтов EF BB BF
  • UTF-16 BE c BOM записывает в начало файла метку U+FEFF в виде 2-х байтов FE FF
  • UTF-16 LE c BOM записывает в начало файла метку U+FEFF в виде 2-х байтов FF FE

Согласно стандарту Encoding Standard, UTF-8 является самой подходящей кодировкой для обмена юникодом.

Unicode в операционных системах

Современные ОС более менее поддерживают юникод.

К примеру, ОС семейства Windows NT, а точнее с Windows 2000, поддерживают ввод и вывод символов юникод. Для байтового представления символов юникод используется UTF-16 LE. При этом Windows не ограничивает прикладные программы касательно кодирования текстовых файлов, позволяя им использовать как UTF-16 LE, так и UTF-16 BE посредством установки и последующей трактовки метки последовательности байтов. Однако внутренний формат Windows — это UTF-16 LE.

Unix-подобные ОС, в том числе GNU/Linux, BSD, OS X, используют для представления юникода кодировку UTF-8.

Unicode Basics

Unicode is a standard that precisely defines a character set as well as a small number of encodings for it. It enables you to handle text in any language efficiently. It allows a single application executable to work for a global audience. ICU, like Java™, Microsoft® Windows NT™, Windows™ 2000 and other modern systems, provides Internationalization solutions based on Unicode.

This chapter is intended as an introduction to codepages in general and Unicode in particular. For further information, see:

Go to the online ICU demos to see how a Unicode-based server application can handle text in many languages and many encodings.

Traditional Character Sets and Unicode

Representing text-format data in computers is a matter of defining a set of characters and assigning each of them a number and a bit representation. Underlying this basic idea are three related concepts:

A character set or repertoire is an unordered collection of characters that can be represented by numeric values.

A coded character set maps characters from a character set or repertoire to numeric values.

A character encoding scheme defines the representation of numeric values from one or more coded character sets in bits and bytes.

For simple encodings such as ASCII, the last two concepts are basically the same: ASCII assigns 128 characters and control codes to consecutive numbers from 0 to 127. These characters and control codes are encoded as simple, unsigned, binary integers. Therefore, ASCII is both a coded character set and a character encoding scheme.

ASCII only encodes 128 characters, 33 of which are control codes rather than graphic, displayable characters. It was designed to represent English-language text for an American user base, and is therefore insufficient for representing text in almost any language other than American English. In fact, most traditional encodings were limited to one or few languages and scripts.

ASCII offered a natural way to extend it: Designed in the 1960’s to work in systems with 7-bit bytes while most computers and Internet protocols since the 1970’s use 8-bit bytes, the extra bit allowed another 128 byte values to represent more characters. Various encodings were developed that supported different languages. Some of these were based on ASCII, others were not.

Languages such as Japanese need to encode considerably more than 256 characters. Various encoding schemes enable large character sets with thousands or tens of thousands of characters to be represented. Most of those encodings are still byte-based, which means that many characters require two or more bytes of storage space. A process must be developed to interpret some byte values.

Various character sets and encoding schemes have been developed independently, cover only one or few languages each, and are incompatible. This makes it very difficult for a single system to handle text in more than one language at a time, and especially difficult to do so in a way that is interoperable across different systems.

Generally, the minimum requirement for the interoperable exchange of text data is that the encoding (character set & encoding scheme) must be properly specified in the document and in the protocol. For example, email/SMTP and HTML/HTTP provide the means to specify the “charset”, as it is called in Internet standards. However, very often the encoding is not specified, specified incorrectly, or the sender and receiver disagree on its implementation.

The ISO 2022 encoding scheme was created to store text in many different languages. It allows other encodings to be embedded by first announcing them and then switching between them. Full support for all features and possible encodings with ISO 2022 requires complicated processing and the need to support many encodings. For East Asian languages, subsets were developed that cover only one language or a few at a time, but they are much more manageable. ISO 2022 is not well-suited for use in internal processing. It is designed for data exchange.

Glyphs versus Characters

Programmers often need to distinguish between characters and glyphs. A character is the smallest semantic unit in a writing system. It is an abstract concept such as the letter A or the exclamation point. A glyph is the visual presentation of one or more characters, and is often dependent on adjacent characters.

There is not always a one-to-one mapping between characters and glyphs. In many languages (Arabic is a prime example), the way a character looks depends heavily on the surrounding characters. Standard printed Arabic has as many as four different printed representations (glyphs) for every letter of the alphabet. In many languages, two or more letters may combine together into a single glyph (called a ligature), or a single character might be displayed with more than one glyph.

Despite the different visual variants of a particular letter, it still retains its identity. For example, the Arabic letter heh has four different visual representations in common use. Whichever one is used, it still keeps its identity as the letter heh. It is this identity that Unicode encodes, not the visual representation. This also cuts down on the number of independent character values required.

Overview of Unicode

Unicode was developed as a single-coded character set that contains support for all languages in the world. The first version of Unicode used 16-bit numbers, which allowed for encoding 65,536 characters without complicated multibyte schemes. With the inclusion of more characters, and following implementation needs of many different platforms, Unicode was extended to allow more than one million characters. Several other encoding schemes were added. This introduced more complexity into the Unicode standard, but far less than managing a large number of different encodings.

Starting with Unicode 2.0 (published in 1996), the Unicode standard began assigning numbers from 0 to 10ffff16,which requires 21 bits but does not use them completely. This gives more than enough room for all written languages in the world. The original repertoire covered all major languages commonly used in computing. Unicode continues to grow, and it includes more scripts.

The design of Unicode differs in several ways from traditional character sets and encoding schemes:

Its repertoire enables users to include text efficiently in almost all languages within a single document.

It can be encoded in a byte-based way with one or more bytes per character, but the default encoding scheme uses 16-bit units that allow much simpler processing for all common characters.

Many characters, such as letters with accents and umlauts, can be combined from the base character and accent or umlaut modifiers. This combining reduces the number of different characters that need to be encoded separately. “Precomposed” variants for characters that existed in common character sets at the time were included for compatibility.

Characters and their usage are well-defined and described. While traditional character sets typically only provide the name or a picture of a character and its number and byte encoding, Unicode has a comprehensive database of properties available for download. It also defines a number of processes and algorithms for dealing with many aspects of text processing to make it more interoperable.

The early inclusion of all characters of commonly used character sets makes Unicode a useful “pivot” point for converting between traditional character sets, and makes it feasible to process non-Unicode text by first converting into Unicode, process the text, and convert it back to the original encoding without loss of data.

The first 128 Unicode code point values are assigned to the same characters as in US-ASCII. For example, the same number is assigned to the same character. The same is true for the first 256 code point values of Unicode compared to ISO 8859-1 (Latin-1) which itself is a direct superset of US-ASCII. This makes it easy to adapt many applications to Unicode because the numbers for many syntactically important characters are the same.

Character Encoding Forms and Schemes for Unicode

Unicode assigns characters a number from 0 to 10FFFF16, giving enough elbow room to allow for unambiguous encoding of every character in common use. Such a character number is called a “code point”.

Unicode code points are just non-negative integer numbers in a certain range. They do not have an implicit binary representation or a width of 21 or 32 bits. Binary representation and unit widths are defined for encoding forms.

For internal processing, the standard defines three encoding forms, and for file storage and protocols, some of these encoding forms have encoding schemes that differ in their byte ordering. The difference between an encoding form and an encoding scheme is that an encoding form maps the character set codes to values that fit into internal data types (like a short in C), while an encoding scheme maps to bits and bytes. For traditional encodings, they are the same since the encoding forms already map to bytes.

The different Unicode encoding forms are optimized for a variety of different uses:

UTF-16, the default encoding form, maps a character code point to either one or two 16-bit integers.

UTF-8 is a byte-based encoding that offers backwards compatibility with ASCII-based, byte-oriented APIs and protocols. A character is stored with 1, 2, 3, or 4 bytes.

UTF-32 is the simplest, but most memory-intensive encoding form: It uses one 32-bit integer per Unicode character.

SCSU is an encoding scheme that provides a simple compression of Unicode text. It is designed only for input and output, not for internal use.

ICU uses UTF-16 internally. ICU 2.0 fully supports supplementary characters (with code points 1000016..10FFFF16). Older versions of ICU provided only partial support for supplementary characters.

For input/output, character encoding schemes define a byte serialization of text. UTF-8 is itself both an encoding form, and an encoding scheme because it is byte-based. For each of UTF-16 and UTF-32, there are two variants defined: one that serializes the code units in big-endian byte order (most significant byte first), and one that serializes the code units in little-endian byte order (least significant byte first). The corresponding encoding schemes are called UTF-16BE, UTF-16LE, UTF-32BE, and UTF-32LE.

The names “UTF-16” and “UTF-32” are ambiguous. Depending on context, they refer either to character encoding forms where 16/32-bit words are processed and are naturally stored in the platform endianness, or they refer to the IANA-registered charset names, i.e., to character encoding schemes or byte serializations. In addition to simple byte serialization, the charsets with these names also use optional Byte Order Marks (see Serialized Formats below).

Overview of UTF-16

The default encoding form of the Unicode Standard uses 16-bit code units. Code point values for the most common characters are in the range of 0 to FFFF16 and are encoded with just one 16-bit unit of the same value. Code points from 1000016 to 10FFFF16 are encoded with two code units that are often called “surrogates”, and they are called a “surrogate pair” when, together, they correctly encode one Unicode character. The first surrogate in a pair must be in the range D80016 to DBFF16, and the second one must be in the range DC0016 to DFFF16. Every Unicode code point has only one possible UTF-16 encoding with either one code unit that is not a surrogate or with a correct pair of surrogates. The code point values D80016 to DFFF16 are set aside just for this mechanism and will never, by themselves, be assigned any characters.

Most commonly used characters have code points below FFFF16, but Unicode 3.1 assigns more than 40,000 supplementary characters that make use of surrogate pairs in UTF-16.

Note that comparing UTF-16 strings lexically based on their 16-bit code units does not result in the same order as comparing the code points. This is not usually an issue since only rarely-used characters are affected. Most processes do not rely on the same results in such comparisons. Where necessary, a simple modification to a string comparison can be performed that still allows efficient code unit-based comparisons and makes them compatible with code point comparisons. ICU has C and C++ API functions for this.

Overview of UTF-8

To meet the requirements of byte-oriented, ASCII-based systems, the Unicode Standard defines UTF-8. UTF-8 is a variable-length, byte-based encoding that preserves ASCII transparency.

UTF-8 maintains transparency for all the ASCII code values (0..127). These values do not appear in any byte of a transformed result except as the direct representation of the ASCII values. Thus, ASCII text is also UTF-8 text.

Characteristics of UTF-8 include:

Unicode code points 0 to 7F16 are each encoded with a single byte of the same value. Therefore, ASCII characters take up 50% less space with UTF-8 encoding than with UTF-16.

All other code points are encoded with multibyte sequences, with the first byte (lead byte) indicating the number of bytes that follow (trail bytes). This results in very efficient parsing. The lead bytes are in the range c016 to fd16, the trail bytes are in the range 8016 to bf16. The byte values fe16 and FF16 are never used.

UTF-8 is relatively compact and resource conservative in its use of the bytes required for encoding text in European scripts, but uses 50% more space than UTF-16 for East Asian text. Code points up to 7FF16 take up two bytes, code points up to FFFF16 take up three (50% more memory than UTF-16), and all others four.

Binary comparisons of UTF-8 strings based on their bytes result in the same order as comparing code point values.

Overview of UTF-32

The UTF-32 encoding form always uses one single 32-bit integer per Unicode code point. This results in a very simple encoding.

The drawback is its memory consumption: Since code point values use only 21 bits, one-third of the memory is always unused, and since most commonly used characters have code point values of up to FFFF16, they take up only one 16-bit unit in UTF-16 (50% less) and up to three bytes in UTF-8 (25% less).

UTF-32 is mainly used in APIs that are defined with the same data type for both code points and code units. Modern versions of the C standard library that support Unicode use a 32-bit wchar_t with UTF-32 semantics.

Overview of SCSU

SCSU (Standard Compression Scheme for Unicode) is designed to reduce the size of Unicode text for both input and output. It is a simple compression that transforms the text into a byte stream. It typically uses one byte per character in small scripts, and two bytes per character in large, East Asian scripts.

It is usually shorter than any of the UTFs. However, SCSU is stateful, which makes it unsuitable for internal processing. It also uses all possible byte values, which might require additional processing for protocols such as SMTP (email).

Other Unicode Encodings

Other Unicode encodings have been developed over time for various purposes. Most of them are implemented in ICU, see source/data/mappings/convrtrs.txt

BOCU-1: Binary-Ordered Compression of Unicode An encoding of Unicode that is about as compact as SCSU but has a much smaller amount of state. Unlike SCSU, it preserves code point order and can be used in 8bit emails without a transfer encoding. BOCU-1 does not preserve ASCII characters in ASCII-readable form. See Unicode Technical Note #6 .

UTF-7: Designed for 7bit emails; simple and not very compact. Since email systems have been 8-bit safe for several years, UTF-7 is not necessary any more and not recommended. Most ASCII characters are readable, others are base64-encoded. See RFC 2152 .

IMAP-mailbox-name: A variant of UTF-7 that is suitable for expressing Unicode strings as ASCII characters for Unix filenames. The name “IMAP-mailbox-name” is specific to ICU! See RFC 2060 INTERNET MESSAGE ACCESS PROTOCOL — VERSION 4rev1 section 5.1.3. Mailbox International Naming Convention.

UTF-EBCDIC: An EBCDIC-friendly encoding that is similar to UTF-8. See Unicode Technical Report #16 . As of ICU 2.6, UTF-EBCDIC is not implemented in ICU.

CESU-8: Compatibility Encoding Scheme for UTF-16: 8-Bit An incompatible variant of UTF-8 that preserves 16-bit-Unicode (UTF-16) string order instead of code point order. Not for open interchange. See Unicode Technical Report #26 .

Programming using UTFs

Programming using any of the UTFs is much more straightforward than with traditional multi-byte character encodings, even though UTF-8 and UTF-16 are also variable-width encodings.

Within each Unicode encoding form, the code unit values for singletons (code units that alone encode characters), lead units, and for trailing units are all disjointed. This has crucial implications for implementations. The following lists these implications:

Determines the number of units for one code point using the lead unit. This is especially important for UTF-8, where there can be up to 4 bytes per character.

Determines boundaries. If ICU users randomly access text, you can always determine the nearest code-point boundaries with a small number of machine instructions.

Does not have any overlap. If ICU users search for string A in string B, you never get a false match on code points. Users do not need to convert to code points for string searching. False matches never occurs since the end of one sequence is never the same as the start of another sequence. Overlap is one of the biggest problems with common multi-byte encodings like Shift-JIS. All the UTFs avoid this problem.

Uses simple iteration. Getting the next or previous code point is straightforward, and only takes a small number of machine instructions.

Can use UTF-16 encoding, which is actually fully symmetric. ICU users can determine from any single code unit whether it is the first, last, or only one for a code point. Moving (iterating) in either direction through UTF-16 text is equally fast and efficient.

Uses slow indexing by code points. This indexing procedure is a disadvantage of all variable-width encodings. Except in UTF-32, it is inefficient to find code unit boundaries corresponding to the nth code point or to find the code point offset containing the nth code unit. Both involve scanning from the start of the text or from a last known boundary. ICU, like most common APIs, always indexes by code units. It counts code units and not code points.

Conversion between different UTFs is very fast. Unlike converting to and from legacy encodings like Latin-2, conversion between UTFs does not require table look-ups.

ICU provides two basic data type definitions for Unicode. UChar32 is a 32-bit type for code points, and used for single Unicode characters. It may be signed or unsigned. It is the same as wchar_t if it is 32 bits wide. UChar is an unsigned 16-bit integer for UTF-16 code units. It is the base type for strings ( UChar * ), and it is the same as wchar_t if it is 16 bits wide.

Some higher-level APIs, used especially for formatting, use characters closer to a representation for a glyph. Such “user characters” are also called “graphemes” or “grapheme clusters” and require strings so that combining sequences can be included.

Serialized Formats

In files, input, output, and network protocols, text must be accompanied by the specification of its character encoding scheme for a client to be able to interpret it correctly. (This is called a “charset” in Internet protocols.) However, an encoding scheme specification is not necessary if the text is only used within a single platform, protocol, or application where it is otherwise clear what the encoding is. (The language and text directionality should usually be specified to enable spell checking, text-to-speech transformation, etc.)

The discussion of encoding specifications in this section applies to standard Internet protocols where charset name strings are used. Other protocols may use numeric encoding identifiers and assign different semantics to those identifiers than Internet protocols.

Typically, the encoding specification is done in a protocol- and document format-dependent way. However, the Unicode standard offers a mechanism for tagging text files with a “signature” for cases where protocols do not identify character encoding schemes.

The character ZERO WIDTH NO-BREAK SPACE (FEFF16) can be used as a signature by prepending it to a file or stream. The alternative function of U+FEFF as a format control character has been copied to U+2060 WORD JOINER, and U+FEFF should only be used for Unicode signatures.

The different character encoding schemes generate different, distinct byte sequences for U+FEFF:

UTF-32BE: 00 00 FE FF

UTF-32LE: FF FE 00 00

UTF-7: 2B 2F 76 ( 38 39 2B 2F )

ICU provides the function ucnv_detectUnicodeSignature() for Unicode signature detection.

There is no signature for CESU-8 separate from the one for UTF-8. UTF-8 and CESU-8 encode U+FEFF and in fact all BMP code points with the same bytes. The opportunity for misidentification of one as the other is one of the reasons why CESU-8 should only be used in limited, closed, specific environments.

In UTF-16 and UTF-32, where the signature also distinguishes between big-endian and little-endian byte orders, it is also called a byte order mark (BOM). The signature works for UTF-16 since the code point that has the byte-swapped encoding, FFFE16, will never be a valid Unicode character. (It is a “non-character” code point.) In Internet protocols, if an encoding specification of “UTF-16” or “UTF-32” is used, it is expected that there is a signature byte sequence (BOM) that identifies the byte ordering, which is not the case for the encoding scheme/charset names with “BE” or “LE”.

If text is specified to be encoded in the UTF-16 or UTF-32 charset and does not begin with a BOM, then it must be interpreted as UTF-16BE or UTF-32BE, respectively.

A signature is not part of the content, and must be stripped when processing. For example, blindly concatenating two files will give an incorrect result.

If a signature was detected, then the signature “character” U+FEFF should be removed from the Unicode stream after conversion. Removing the signature bytes before conversion could cause the conversion to fail for stateful encodings like BOCU-1 and UTF-7.

Whether a signature is to be recognized or not depends on the protocol or application.

If a protocol specifies a charset name, then the byte stream must be interpreted according to how that name is defined. Only the “UTF-16” and “UTF-32” names include recognition of the byte order marks that are specific to them (and the ICU converters for these names do this automatically). None of the other Unicode charsets are defined to include any signature/BOM handling.

Читать:
Как найти центр треугольника по координатам вершин

If no charset name is provided, for example for text files in most filesystems, then applications must usually rely on heuristics to determine the file encoding. Many document formats contain an embedded or implicit encoding declaration, but for plain text files it is reasonable to use Unicode signatures as simple and reliable heuristics. This is especially common on Windows systems. However, some tools for plain text file handling (e.g., many Unix command line tools) are not prepared for Unicode signatures.

The Unicode Standard Is An Industry Standard

The Unicode standard is an industry standard and parallels ISO 10646-1. Around 1993, these two standards were effectively merged into the same character set standard. Both standards have the same character repertoire and the same encoding forms and schemes.

One difference used to be that the ISO standard defined code point values to be from 0 to 7FFFFFFF16, not just up to 10FFFF16. The ISO work group decided to add an amendment to the standard. The amendment removes this difference by declaring that no characters will ever be assigned code points above 10FFFF16. The main reason for the ISO work group’s decision is interoperability between the UTFs. UTF-16 can not encode any code points above this limit.

This means that the code point space for both Unicode and ISO 10646 is now the same! These changes to ISO 10646 have been made recently and should be complete in the edition ISO 10646:2003 which also combines all parts of the standard into one.

The former, larger code space is the reason why the ISO definition of UTF-8 specifies sequences of five and six bytes to cover that whole range.

Another difference is that the ISO standard defines encoding forms “UCS-4” and “UCS-2”. UCS-4 is essentially UTF-32 with a theoretical upper limit of 7FFFFFFF16, using 31 out of the 32 bits. However, in practice, the ISO committee has accepted that the characters above 10FFFF will not be encoded, so there is essentially no difference between the forms. The “4” stands for “four-byte form”.

UCS-2 is a subset of UTF-16 that is limited to code points from 0 to FFFF, excluding the surrogate code points. Thus, it cannot represent the characters with code points above FFFF (called supplementary characters).

There is no conversion necessary between UCS-2 and UTF-16. The difference is only in the interpretation of surrogates.

The standards differ in what kind of information they provide: The Unicode standard provides more character properties and describes algorithms etc., while the ISO standard defines collections, subsets and similar.

The standards are synchronized, and the respective committees work together to add new characters and assign code point values.

Name already in use

If nothing happens, download GitHub Desktop and try again.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching Xcode

If nothing happens, download Xcode and try again.

Launching Visual Studio Code

Your codespace will open once ready.

There was a problem preparing your codespace, please try again.

Latest commit

Git stats

Files

Failed to load latest commit information.

README.md

Unicode и кодировки UTF-8, UTF-16. Основная информация, необходимая для разработки ПО.

История и введение

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

До появления стандарта unicode, в мире было множество разных схем кодирования символов. Однако, у них было значительное количество недостатков: не было универсальной кодировки — многие схемы были несовместимы по отношению друг к другу; они не описывали все необходимые пользователю символы (наличие множества языков). Для любого компьютера, особенно сервера, необходимо было поддерживать несколько кодировок, но даже при этом существовал риск того, что данные, при передаче, могли оказаться поврежденными.

Одна из наиболее популярных кодировок в тот момент это — ASCII. Всего в систему ASCII включены 128 символов — она использует все комбинации семи битов (от 0000000 до 1111111). Например, последовательность бит двоичной системы 01000001 в изложенной кодировке кодирует символ “A”, или 01000010 кодирует символ “B”. Основная проблема ASCII заключалась в том, что эта кодировка неплохо работала только в англоязычной среде. Использовать её с другими языками было затруднительно.

С целью разрешить несистематизированность и попытаться унифицировать систему кодирования символов, которая могла бы использоваться для множества языков, в 1991 году некоммерческой организацией «Консорциум Юникода» был разработан и предложен стандарт Unicode. В этот консорциум входят такие лидеры компьютерной индустрии, как Apple, HP, IBM, Microsoft, Oracle и т.д. поэтому, эта схема кодирования используется почти во всех современных технологиях и стандартах, а консорциум ставит своими задачами — развитие и распространение этой системы. В результате использования этого стандарта, можно значительно сэкономить на поддержке программного обеспечения. Главной особенностью Unicode является то, что она присваивает уникальный код любому символу, независимо от платформы, программы или языка.

Начиная с октября 1991 года, стандарт постоянно развивается. Последняя версия (12.1) вышла в мае 2019 года. В каждой новой версии появляется больше символов, иероглифов из восточных языков, эмодзи и различных письменностей.

Стандарт состоит из двух основных частей: универсального набора символов (англ. Universal character set, UCS) и семейства кодировок (англ. Unicode transformation format, UTF). Универсальный набор символов перечисляет допустимые по стандарту Unicode символы и присваивает каждому символу код в виде неотрицательного целого числа, записываемого обычно в шестнадцатеричной форме с префиксом U+, например, U+040F. Семейство кодировок определяет способы преобразования кодов символов для передачи в потоке или в файле.

Общее количество кодовых позиций в unicode — 1 112 064, несмотря на значительно больший объём возможных вариантов (2^31 = 2 147 483 648). Это было сделано для совместимости разных представлений: UTF-8, UTF-16 и UTF-32. В последней версии Unicode, за май 2019, количество всех символов составляет 137 994.

Важно отметить, что стандарт unicode полностью совместим основным предшественником — ASCII. Есть конкретная область с кодами от U+0000 до U+007F, которая содержит символы набора ASCII, соответствующие их кодами в ASCII.

Простыми словами, Unicode – это просто огромная таблица соответствия символов и чисел, а различные UTF кодировки определяют, как эти числа переводятся в биты.

Формы представления unicode: UTF-8 и UTF-16

Стандарт Unicode в настоящее время реализуется различными кодировками, самые распространённые — UTF-8 и UTF-16, кодировки с переменной длиной кодирования.

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

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

Любой символ юникода в кодировке UTF-16 может быть закодирован либо одной парой байт или кодовой парой, либо двумя.

Количество символов, кодируемых одной кодовой парой может быть 65 535 (2^16), что полностью совпадает с базовым блоком юникода. Все символы находящиеся в этом блоке в кодировке UTF-16 будут закодированы одной кодовой парой (двумя байтами), тут все просто.

Для символов, находящихся за пределами базового unicode диапазона потребуется уже две кодовые пары (4 байта). И механизм их кодирования немного сложнее.

Для пояснения, необходимо ввести понятие “суррогатной пары”. Суррогатная пара — это две кодовые пары используемые для кодирования одного символа (итого 4 байта). Для таких суррогатных пар в таблице юникода отведен специальный диапазон от D800 до DFFF. Это значит, что при преобразовании кодовой пары из байтового вида в шестнадцатеричный вы получаете число из этого диапазона, то перед вами не самостоятельный символ, а суррогатная пара.

Чтобы закодировать символ из диапазона 1000010FFFF (то есть символ для которого нужно использовать более одной кодовой пары) нужно:

  1. Из кода символа вычесть 10000 (шестнадцатеричное) (это наименьшее число из диапазона 1000010FFFF)
  2. В результате первого пункта будет получено число не больше FFFFF, занимающее до 20 бит
  3. Ведущие 10 бит из полученного числа суммируются с D800 (начало диапазона суррогатных пар в юникоде)
  4. Следующие 10 бит суммируются с DC00 (тоже число из диапазона суррогатных пар)
  5. После этого получатся 2 суррогатные пары по 16 бит, первые 6 бит в каждой такой паре отвечают за определение того что это суррогат,
  6. Десятый бит в каждом суррогате отвечает за его порядок если это 1 то это первый суррогат, если 0, то второй.

(Подробнее в дополнительной литературе: Как работают кодировки текста. Откуда появляются «кракозябры». Принципы кодирования. Обобщение и детальный разбор).

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

  1. С помощью одной кодовой точки U+00E9.
  2. С помощью комбинации буквы e и знака акута, то есть — с помощью двух кодовых точек — U+0065 и U+0301 (комбинации символов).

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

Существуют четыре стандартных формы (алгоритма) нормализации:

  • NFC: Normalization Form Canonical Composition. Форма нормализации C — алгоритм, согласно которому последовательно выполняются каноническая декомпозиция и каноническая композиция.
  • NFD: Normalization Form Canonical Decomposition. Каноническая декомпозиция — алгоритм, согласно которому выполняется рекурсивное разложение составных символов на последовательность из одного или нескольких простых символов в соответствии с таблицами декомпозиции. Рекурсивное потому, что в процессе разложения составной символ может быть разложен на несколько других, некоторые из которых тоже являются составными, и к которым применяется дальнейшее разложение.
  • NFKC: Normalization Form Compatibility Composition. Форма нормализации KC — алгоритм, согласно которому последовательно выполняются совместимая декомпозиция (алгоритм NFKD) и каноническая композиция (алгоритм NFC).
  • NFKD: Normalization Form Compatibility Decomposition. Форма нормализации KD — совместимая декомпозиция — алгоритм, согласно которому последовательно выполняются каноническая декомпозиция и замены символов текста по таблицам совместимой декомпозиции.

Чаще всего используется форма нормализации NFC. При использовании этого алгоритма все символы сначала подвергаются декомпозиции (процесс, при котором все буквы по возможности разбиваются на модификаторы), после чего все комбинирующиеся последовательности подвергаются повторной композиции (процесс, при котором все буквы по возможности объединяются в одну) в порядке, определяемом стандартом. Для практического применения можно выбрать любую форму. Главное — применять её последовательно. В результате поступление на вход программы одних и тех же данных всегда будет приводить к одному и тому же результату.

Несмотря на наличие такого унифицированного стандарта кодирования символов, у Unicode остаются некоторые нерешенные проблемы:

  • Тексты на китайском, корейском и японском языках имеют традиционное написание сверху вниз, начиная с правого верхнего угла. Переключение горизонтального и вертикального написания для этих языков не предусмотрено в Unicode — это должно осуществляться средствами языков разметки или внутренними механизмами текстовых процессоров.
  • Наличие или отсутствие в Unicode разных начертаний одного и того же символа в зависимости от языка. Нужно следить, чтобы текст всегда был правильно помечен как относящийся к тому или другому языку.
  • Так, китайские иероглифы могут иметь разные начертания в китайском, японском (кандзи) и корейском (ханча), но при этом в Unicode обозначаются одним и тем же символом (так называемая CJK-унификация), хотя упрощённые и полные иероглифы всё же имеют разные коды.
  • Аналогично, русский и сербский языки используют разное начертание курсивных букв п и т (в сербском они выглядят как и и ш, см. сербский курсив).
  • Перевод из строчных букв в заглавные тоже зависит от языка.
  • Даже с арабскими цифрами есть определённые типографские тонкости: цифры бывают «прописными» и «строчными», пропорциональными и моноширинными — для Unicode разницы между ними нет. Подобные нюансы остаются за программным обеспечением.

Некоторые недостатки связаны не с самим Unicode, а с возможностями обработчиков текста.

  • Файлы нелатинского текста в Unicode всегда занимают больше места, так как один символ кодируется не одним байтом, как в различных национальных кодировках, а последовательностью байтов (исключение составляет UTF-8 для языков, алфавит которых укладывается в ASCII, а также наличие в тексте символов двух и более языков, алфавит которых не укладывается в ASCII). Файл шрифта, необходимый для отображения всех символов таблицы Unicode, занимает сравнительно много места в памяти и требует больших вычислительных ресурсов, чем шрифт только одного национального языка пользователя. С увеличением мощности компьютерных систем и удешевлением памяти и дискового пространства эта проблема становится всё менее существенной; тем не менее, она остаётся актуальной для портативных устройств, например, для мобильных телефонов.
  • Хотя поддержка Unicode реализована в наиболее распространённых операционных системах, до сих пор не всё прикладное программное обеспечение поддерживает корректную работу с ним. В частности, не всегда обрабатываются метки порядка байтов (BOM) и плохо поддерживаются диакритические символы. Проблема является временной и есть следствие сравнительной новизны стандартов Unicode (в сравнении с однобайтовыми национальными кодировками).
  • Производительность всех программ обработки строк (в том числе и сортировок в БД) снижается при использовании Unicode вместо однобайтовых кодировок.

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

Например, существует атака, описанная OWASP, направленная ​​на поиск недостатков в механизме декодирования, реализованном в приложениях при декодировании формата данных Unicode. Злоумышленник может использовать эту технику для кодирования определенных символов в URL-адресе, чтобы обойти фильтры приложений, таким образом получая доступ к ограниченным ресурсам на веб-сервере или принудительно просматривая защищенные страницы.

Другой пример уязвимости, найденной сравнительно недавно, это брешь в процедуре восстановления забытого пароля на GitHub, которая позволяла злоумышленнику получить пароль от чужого аккаунта.

В рамках этой процедуры восстановления, выполнялось сравнение введённого адреса электронной почты с адресом, который хранится в базе. Алгоритм проверки:

  1. Введённый адрес переводится в нижний регистр с помощью метода toLowerCase .
  2. Введённый адрес сравнивается с адресом в базе зарегистрированных пользователей.
  3. Если найдено совпадение, пароль из базы данных высылается на введённый адрес.

Очевидно, разработчики не знали о коллизии трансляции адресов при использовании метода toLowerCase . В данном случае исправить ошибку просто. Достаточно отправить пароль не на введённый адрес, а на адрес из базы данных.

Связанные с Юникодом баги имеют такое свойство, что их можно встретить в любом приложении, которое обрабатывает текст, введённый пользователем. Уязвимости есть и в веб-приложениях, и в нативных программах под Android и iOS.

Большую известность получила ошибка, связанная с воспроизведением символов Unicode для индийского языка телугу. Проблема возникала на некоторых версиях iOS в приложениях, использующих дефолтный шрифт San Francisсo. Получив всего несколько символов జ్ఞా, пользователь терял управление над многими приложениями в iOS, включая почту и Facebook. Если один из символов телугу появлялся во всплывающих уведомлениях, то блокировался SpringBoard — приложение, отвечающее за главный экран в iOS.

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

Подводя итог, можно отметить следующие важные моменты касательно Unicode:

UTF-8 vs UTF-16. Несколько советов программистам

С появлением первых устройств цифровой передачи информации и электронно-вычислительных машин возникла задача кодирования текстовых символов с помощью последовательностей единиц и нулей. Минимальная единица представления информации – байт. Исходя их этого в 1963 году в США разработана, стандартизована, а впоследствии расширена кодовая таблица ASCII (American standard code for information interchange), использовавшая 8 битную кодировку. В первую очередь с помощью этой таблицы предполагалось кодирование цифр и букв английского языка. Первые 128 символов таблицы представлены на рис.1:

Рис.1. Первые 128 символов таблицы ASCII.

Номер ячейки в таблице (рис.1) является кодом символа. В качестве примера рассмотрим кодирование слова Hello. Номера ячеек таблицы ASCII, в которых размещены буквы: 72 (H), 101 (e), 108 (l), 111 (o). Код слова в бинарном представлении выглядит следующим образом:

00010010 (H) 10100110 (e) 00110110 (l) 00110110 (l) 11110110 (o) (старший бит справа).

Выделенные подчеркиванием и жирным коды в двоичном представлении соответствуют номерам ячеек в таблице (рис.1). Алгоритм формирования кода следующий:

1. Выделены жирным – биты управления кодированием (префикс). 010 – кодируется заглавная буква алфавита, 011 – строчная.

2. Выделены подчеркиванием – порядковые номера букв в английском алфавите.

Таким образом, с помощью первых 128 ячеек таблицы ASCII могли быть закодированы основные символы, цифры и буквы английского языка. Остальные 128 ячеек (8 битная кодировка позволяет закодировать 256 символов) могли использоваться для кодирования других языков. Однако, учитывая разнообразие символов и языков, 8 бит недостаточно.

Стандарт Юникод

Консорциум Unicode (Юникод) – некоммерческая организация, главной задачей которой являлась разработка стандарта кодирования (стандарт Юникод) с поддержкой наибольшего числа языков и символов служебного характера. Принцип кодирования на основе таблицы сохранился, а таблица (таблица Юникод) была значительно расширена.

Стандарт Юникод предоставляет пользователям таблицу Юникод и способы кодирования символов.

Символы таблицы Юникод являются элементами «универсального набора символов» UCS (Universal Coded Character Set), определенного международным стандартом ISO/IEC 10646. Таблица Юникод каждому символу UCS сопоставляет кодовую точку, которая является номером ячейки таблицы, содержащей символ.

Способы кодирования символов таблицы Юникод, т.е. преобразования номеров ячеек таблицы Юникод в бинарные коды, составляют кодовое пространство, состоящее из трех кодов семейства UTF (Unicode Transformation Format): UTF-8, UTF-16 и UTF-32

UTF-8 – стандарт кодирования, преобразующий номера ячеек таблицы Юникод в бинарные коды с использованием переменного количества бит: 8, 16, 24 или 32.

UTF-16 – стандарт кодирования, преобразующий номера ячеек таблицы Юникод в бинарные коды с использованием переменного количества бит:16 или 32.

Коды UTF-8 и UTF-16 используют разные алгоритмы кодирования набора символов UCS.

Стандарт кодирования UTF-8

Стандарт закреплен в RFC (Request For Comments) 3629. Алгоритм кодирования согласно RFC:

0xxxxxxx

110xxxxx 10xxxxxx

1110xxxx 10xxxxxx 10xxxxxx

11110xx 10xxxxxx 10xxxxxx 10xxxxxx

Старший бит слева. Началом кода является управляющий символ (выделен жирным):

0 – используется 8-битная кодировка,

110 – используется 16-битная кодировка,

1110 – используется 24-битная кодировка,

11110 – используется 32 битная кодировка.

В начале каждого последующего байта – биты 10 – управляющий символ (выделен подчеркиванием), означающий продолжение кодирования.

Первые 128 ячеек таблицы Юникод повторяют таблицу ASCII. Для кодирования заглавных и строчных букв русского алфавита используются ячейки с номерами 1040-1103.

Рассмотрим пример кодирования фразы «Папа Hello».

Код в бинарном виде (старший бит справа):

00001011 11111001 (П) 00001011 00001101 (а) 00001011 11111101 (п) 00001011 00001101 (а) 00000100 (пробел) 00010010 (H) 10100110 (e) 00110110 (l) 00110110 (l) 11110110 (o).

Букве П русского алфавита согласно таблицы Юникод соответствует номер 1055, в бинарном представлении 10000011111 – 11 бит. Соответственно данный символ может быть закодирован двумя байтами с использованием префикса 110 – для первого байта и 10 – для второго байта. Английские буквы слова Hello кодируются 1 байтом, а коды совпадают с кодами в таблице ASCII.

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

Стандарт кодирования UTF-16

В феврале 2000 года опубликован документ RFC 2781, в котором закреплен стандарт UTF-16, позволяющий кодировать символы таблицы Юникод с помощью 16 или 32 битных значений. Символы с номерами 0-55295 и 57344-65535 кодируются с помощью 16 бит без изменений (без использования префиксов), а остальные символы, номера которых в двоичном представлении формируются количеством бит больше 16, кодируются 32 битами с использованием специального алгоритма. Рассмотрим пример кодирования фразы «Папа Hello».

Код в бинарном виде (старший бит справа):

11111000 00100000 (П) 00001100 001000000 (а) 11111100 00100000 (п) 00001100 001000000 (а) 00000100 00000000 (пробел) 00010010 00000000 (H) 10100110 00000000 (e) 00110110 00000000 (l) 00110110 00000000 (l) 111110110 00000000 (o).

Номера букв русского и английского алфавитов таблицы Юникод передаются без изменений при помощи 16 бит, старшие незначащие биты принимают нулевое значение.

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

Рис.2. Буква древнетюркского алфавита.Рис.2. Буква древнетюркского алфавита.

Номер предложенного символа в таблице Юникод – 68620 (0х10COC).

Алгоритм преобразования номера символа в код UTF-16 состоит из нескольких шагов:

Из значения номера символа вычесть число 0х10000. Данная операция позволяет привести размерность бинарного представления номера символа к 20 битам. Для предложенного символа получим: 0х10COC – 0x10000 = 0xC0C.

Для полученного значения выделить старшие 10 бит и младшие 10 бит. В примере число 0хС0С в бинарном виде представляется, как 00000000110000001100, где жирным выделены 10 старших бит, а подчеркиванием – 10 младших.

К шестнадцатеричному значению 0xD800 (11011000 00000000) прибавить значение 0х03 (00000000 00000011), сформированное 10 старшими битами, полученными на предыдущем шаге. 0xD800 + 0х03 = 0хD803 (11011000 00000011) – 16 старших бит кодового слова UTF-16.

К шестнадцатеричному значению 0xDC00 (11011000 00000000) прибавить значение 0х0C (00000000 00001100), сформированное 10 младшими битами, полученными на шаге №2. 0xDС00 + 0х0С = DС0С (11011100 00001100) – 16 младших бит кодового слова UTF-16.

Кодовое слово UTF-16, соответствующее символу в примере, формируется из бит, полученных на шагах 3 и 4: 0хD803DC0C (11011000 00000011 11011100 00001100).

Сравнение стандартов UTF-8 и UTF-16 с точки зрения объема машинной памяти, используемой кодом для представления символов

Результаты сравнения стандартов представлены в таблице 1.

Таблица 1. Результаты сравнения стандартов.

В ячейках таблицы 1 содержится количество бит, требуемое для кодирования одного символа из таблицы Юникод. Видно, что для диапазонов номеров ячеек 128-2047, 65535-1048575 стандарты UTF-8 и UTF-16 используют одинаковое количество бит. Для диапазона 0-127 выгодно использование стандарта UTF-8, например, в случае, если программисту поручили реализовать кодер букв английского алфавита. Для диапазонов 2048-32767 и 32768-65535 выгодно использование стандарта UTF-16, например, в случае, если программисту поручили реализовать кодер иероглифов Бопомофо (занимают в таблице Юникод диапазон ячеек 12549-12589). Кодирование символов таблицы Юникод, расположенных в ячейках, номера которых начинаются от 1048575 возможно только с использованием кодировки UTF-16.

В предыдущих главах приведены примеры кодирования фразы «Папа Hello» стандартами UTF-8 и UTF-16. Кодировкой UTF-8 используются 14 байт, кодировкой UTF-16 20 байт, что связано с избыточностью кодирования англоязычных символов во втором случае из-за использования дополнительного байта 0х00. Можно сделать вывод, что для кодирования текста содержащего набор букв русского и английского алфавитов, предпочтительно использование кодировки UTF-8.

Вывод: в зависимости от языка алфавита может быть выбрана как кодировка UTF-8, так и кодировка UTF-16. Для английского алфавита однозначно более выгодно использование кодировки UTF-8, для русского алфавита буквы представляются одинаковым количеством бит при использовании как одной, так и другой кодировки.

Несколько советов программистам

Допустим, программист решил реализовать текстовый редактор, поддерживающий алфавит языка Бопомофо. Символы данного языка располагаются в таблице Юникод в диапазоне 12549-12589 и, следовательно, программисту необходимо выбрать стандарт UTF-16 для кодирования. Предположим, что для ввода символов решено использовать программную клавиатуру, состоящую из кнопок, каждая из которых соответствует букве алфавита языка. Кнопки – объекты класса button. Нажатие пользователем на какую-либо из кнопок порождает событие, в результате которого приложению становится известен номер ячейки таблицы Юникод. Программисту рекомендуется:

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

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

3. Для хранения номеров ячеек и их бинарных кодов использовать объект класса, позволяющего осуществить это в виде ключ-значение, где ключ – номер ячейки, а значение – бинарный код. Классы, реализующие в языках программирования данный функционал, организуют работу таким образом, чтобы минимизировать время поиска ключа, используя сортировку ключей или хеширование.

Отметим проблему кодирования составных символов, которая является важным техническим аспектом. Например, символ ü может быть интерпретирован, как самостоятельный символ, которому соответствует номер ячейки 252 или может быть скомпонован из двух символов: u, которому соответствует номер ячейки 117 и символа ¨, которому соответствует номер ячейки 776. Программист должен строго придерживаться одного из вариантов представления таких символов иначе побайтовое сравнение строк будет невозможно. Рекомендуется использование второго варианта, который может облегчить поиск составных символов в тексте. Например, если пользователь осуществляет поиск символа u, то ему может быть выведен в качестве результата, как составной символ ü, так и самостоятельный u.

Похожие статьи