Spec-Zone.ru › Elixir 1.16

Исходный код Бинарные данные, строки и списки символов

В разделе "Основные типы" мы немного узнали о строках и использовали функцию is_binary/1 для проверок:

iex> string = "hello"
"hello"
iex> is_binary(string)
true

В этой главе мы более подробно разберём, что такое бинарные данные, как они связаны со строками и что означают значения в одинарных кавычках, 'like this', в Elixir. Хотя строки являются одним из самых распространённых типов данных в языках программирования, они могут быть скрыто сложными и часто неправильно понимаются. Чтобы понять строки в Elixir, нам нужно изучить Unicode и кодировки символов, в частности кодировку UTF-8.

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

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

Unicode организует все символы в своём наборе в таблицы кодов, и каждому символу присваивается уникальный числовой индекс. Этот числовой индекс известен как кодовая точка.

В Elixir вы можете использовать ? перед литерным символом, чтобы отобразить его кодовую точку:

iex> ?a
97
iex> ?ł
322

Обратите внимание, что большинство таблиц кодов Unicode будут ссылаться на кодовую точку с помощью её шестнадцатеричного (hex) представления, например, 97 соответствует 0061 в шестнадцатеричном формате, и мы можем представить любой символ Unicode в строке Elixir, используя обозначение \uXXXX и шестнадцатеричное представление его кодовой точки:

iex> "\u0061" == "a"
true
iex> 0x0061 = 97 = ?a
97

Шестнадцатеричное представление также поможет вам найти информацию о кодовой точке, например, https://codepoints.net/U+0061 содержит информационную страницу о строчной букве a, также известной как кодовая точка 97.

UTF-8 и кодировки

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

Elixir использует UTF-8 для кодирования своих строк, что означает, что кодовые точки закодированы как последовательность 8-битных байтов. UTF-8 — это кодировка символов переменной длины, которая использует от одного до четырёх байтов для хранения каждой кодовой точки. Она способна кодировать все допустимые кодовые точки Unicode. Посмотрим на пример:

iex> string = "héllo"
"héllo"
iex> String.length(string)
5
iex> byte_size(string)
6

Хотя в строке выше 5 символов, она использует 6 байтов, так как для символа é используются два байта.

Примечание: если вы работаете в Windows, существует вероятность, что ваша консоль по умолчанию не использует UTF-8. Вы можете изменить кодировку текущего сеанса, выполнив chcp 65001 перед вводом iex (iex.bat).

Помимо определения символов, UTF-8 также предоставляет понятие графем. Графемы могут состоять из нескольких символов, которые часто воспринимаются как один. Например, эмодзи «женщина-пожарный» представлен комбинацией трёх символов: эмодзи «женщина» (👩), скрытый символ разделителя нулевой ширины и эмодзи «пожарный автомобиль» (🚒):

iex> String.codepoints("👩‍🚒")
["👩", "‍", "🚒"]
iex> String.graphemes("👩‍🚒")
["👩‍🚒"]

Однако Elixir достаточно умён, чтобы понимать, что они воспринимаются как один символ, поэтому длина всё ещё равна единице:

iex> String.length("👩‍🚒")
1

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

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

Если вы хотите увидеть точные байты, в которых строка хранится в файле, распространённый приём — добавить к ней нулевой байт <<0>>:

iex> "hełło" <> <<0>>
<<104, 101, 197, 130, 197, 130, 111, 0>>

В качестве альтернативы, вы можете просмотреть двоичное представление строки, используя IO.inspect/2:

iex> IO.inspect("hełło", binaries: :as_binaries)
<<104, 101, 197, 130, 197, 130, 111>>

Мы немного забегаем вперёд. Давайте поговорим о битовых строках, чтобы узнать, что именно означает конструктор <<>>.

Битовые строки

Хотя мы рассмотрели кодовые точки и кодировку UTF-8, нам всё ещё нужно более глубоко изучить то, как именно мы храним закодированные байты, и здесь мы вводим понятие битовой строки. Битовая строка — это фундаментальный тип данных в Elixir, обозначаемый синтаксисом <<>>/1. Битовая строка — это непрерывная последовательность битов в памяти.

По умолчанию для хранения каждого числа в битовой строке используется 8 бит (т. е. 1 байт), но вы можете вручную указать количество бит, используя модификатор ::n для обозначения размера в n битах, или использовать более подробное объявление ::size(n):

iex> <<42>> == <<42::8>>
true
iex> <<3::4>>
<<3::size(4)>>

Например, десятичное число 3 в двоичном представлении с 4 битами будет 0011, что эквивалентно значениям 0, 0, 1, 1, каждое из которых хранится с использованием 1 бита:

iex> <<0::1, 0::1, 1::1, 1::1>> == <<3::4>>
true

Любое значение, превышающее возможное значение при заданном количестве бит, усекается:

iex> <<1>> == <<257>>
true

Здесь 257 в двоичном представлении будет 100000001, но поскольку мы зарезервировали только 8 бит для его представления (по умолчанию), наиболее значимый бит игнорируется, и значение усекается до 00000001, или просто 1 в десятичном формате.

Бинарные данные

Бинарные данные — это битовая строка, где количество бит делится на 8. Это означает, что любая бинарная строка является битовой строкой, но не каждая битовая строка является бинарной. Мы можем использовать функции is_bitstring/1 и is_binary/1 для демонстрации этого.

iex> is_bitstring(<<3::4>>)
true
iex> is_binary(<<3::4>>)
false
iex> is_bitstring(<<0, 255, 42>>)
true
iex> is_binary(<<0, 255, 42>>)
true
iex> is_binary(<<42::16>>)
true

Мы можем использовать шаблонное сопоставление для бинарных/битовых строк:

iex> <<0, 1, x>> = <<0, 1, 2>>
<<0, 1, 2>>
iex> x
2
iex> <<0, 1, x>> = <<0, 1, 2, 3>>
** (MatchError) no match of right hand side value: <<0, 1, 2, 3>>

Обратите внимание, что, если вы не используете явно модификаторы ::, ожидается, что каждый элемент в бинарном шаблоне будет соответствовать одному байту (ровно 8 битам). Если мы хотим сопоставить бинарные данные неизвестного размера, мы можем использовать модификатор binary в конце шаблона:

iex> <<0, 1, x::binary>> = <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> x
<<2, 3>>

Есть ещё несколько полезных модификаторов для использования при шаблонном сопоставлении с бинарными данными. Модификатор binary-size(n) будет соответствовать n байтам в бинарных данных:

iex> <<head::binary-size(2), rest::binary>> = <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> head
<<0, 1>>
iex> rest
<<2, 3>>

Строка — это бинарные данные, закодированные в UTF-8, где кодовая точка каждого символа закодирована от 1 до 4 байтов. Таким образом, каждая строка является бинарными данными, но из-за правил кодирования стандарта UTF-8 не каждая бинарная строка является допустимой строкой.

iex> is_binary("hello")
true
iex> is_binary(<<239, 191, 19>>)
true
iex> String.valid?(<<239, 191, 19>>)
false

Оператор конкатенации строк <> фактически является оператором конкатенации бинарных данных:

iex> "a" <> "ha"
"aha"
iex> <<0, 1>> <> <<2, 3>>
<<0, 1, 2, 3>>

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

iex> <<head, rest::binary>> = "banana"
"banana"
iex> head == ?b
true
iex> rest
"anana"

Однако помните, что бинарное шаблонное сопоставление работает с байтами, поэтому соответствие строке «über» с многобайтовыми символами не будет соответствовать символу, а будет соответствовать первому байту этого символа:

iex> "ü" <> <<0>>
<<195, 188, 0>>
iex> <<x, rest::binary>> = "über"
"über"
iex> x == ?ü
false
iex> rest
<<188, 98, 101, 114>>

Выше, x соответствовало только первому байту многобайтового символа ü.

Поэтому при шаблонном сопоставлении с использованием строк важно использовать модификатор utf8:

iex> <<x::utf8, rest::binary>> = "über"
"über"
iex> x == ?ü
true
iex> rest
"ber"

Списки символов

Наш обзор битовых строк, бинарных данных и строк почти завершён, но у нас есть ещё один тип данных для объяснения: список символов.

Список символов — это список целых чисел, где все целые числа являются допустимыми кодовыми точками. На практике вы редко будете с ними сталкиваться, только в определённых сценариях, таких как взаимодействие со старыми библиотеками Erlang, которые не принимают бинарные данные в качестве аргументов.

iex> ~c"hello"
~c"hello"
iex> [?h, ?e, ?l, ?l, ?o]
~c"hello"

Символ ~c (мы рассмотрим символы позже в главе "Символы") указывает на то, что мы имеем дело со списком символов, а не с обычной строкой.

Вместо байтов список символов содержит целые кодовые точки. Однако список отображается только как символ, если все кодовые точки находятся в диапазоне ASCII:

iex> ~c"hełło"
[104, 101, 322, 322, 111]
iex> is_list(~c"hełło")
true

Это делается для облегчения взаимодействия с Erlang, хотя это может привести к некоторому неожиданному поведению. Например, если вы храните список целых чисел, которые находятся в диапазоне от 0 до 127, по умолчанию IEx будет интерпретировать это как список символов и отображать соответствующие символы ASCII.

iex> heartbeats_per_minute = [99, 97, 116]
~c"cat"

Вы всегда можете заставить IEx отображать списки символов в виде списков, вызвав функцию inspect/2:

iex> inspect(heartbeats_per_minute, charlists: :as_list)
"[99, 97, 116]"

Кроме того, вы можете преобразовать список символов в строку и обратно, используя функции to_string/1 и to_charlist/1:

iex> to_charlist("hełło")
[104, 101, 322, 322, 111]
iex> to_string(~c"hełło")
"hełło"
iex> to_string(:hello)
"hello"
iex> to_string(1)
"1"

Эти функции полиморфны, то есть они принимают много разных форм: они не только преобразуют списки символов в строки (и наоборот), но также могут преобразовывать целые числа, атомы и т. д.

Конкатенация строк (бинарных данных) использует оператор <>, но списки символов, будучи списками, используют оператор конкатенации списков ++:

iex> ~c"this " <> ~c"fails"
** (ArgumentError) expected binary argument in <> operator but got: ~c"this "
    (elixir) lib/kernel.ex:1821: Kernel.wrap_concatenation/3
    (elixir) lib/kernel.ex:1808: Kernel.extract_concatenations/2
    (elixir) expanding macro: Kernel.<>/2
    iex:1: (file)
iex> ~c"this " ++ ~c"works"
~c"this works"
iex> "he" ++ "llo"
** (ArgumentError) argument error
    :erlang.++("he", "llo")
iex> "he" <> "llo"
"hello"

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

← Предыдущая страница Анонимные функции
Следующая страница → Список ключевых слов и карты

Скачать версию ePub

Создано с помощью ExDoc (v0.32.2) для языка программирования Elixir

© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.16.3/binaries-strings-and-charlists.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API