В «Базовых типах» мы познакомились со строками и использовали функцию is_binary/1
для проверок:
iex> string = "hello"
"hello"
iex> is_binary(string)
true
В этой главе мы узнаем, что такое двоичные данные, как они связаны со строками, и что в Эликсире представляют собой значения в одинарных кавычках 'like this'
.
UTF-8
и Юникод
Строка в UTF-8
представляет собой бинарную последовательность. Для понимания, что мы подразумеваем, следует понять разницу между байтами и кодовыми обозначениями.
Стандарт Юникод связывает кодовые обозначения со многими известными символами. Например, латинская буква a
имеет кодовое обозначение 97
, тогда как буква ł
имеет код 322
. Чтобы записать строку "hełło"
на диск, нужно конвертировать эти коды в байты. Если мы примем за правило, что один байт соответствует одному кодовому обозначению, мы не сможем записать строку "hełło"
, потому что она использует код 322
для ł
, а один байт может представлять число от 0
до 255
. Но, раз мы можем прочитать "hełło"
на экране, есть какой-то способ записать такую строку. Это место, где начинает работать кодировка.
Для представления кодовых обозначений в виде байт, нужно как-то их закодировать. Эликсир выбрал кодировку UTF-8
своей главной и стандартной кодировкой. Когда мы говорим, что UTF-8
закодирован в двоичном виде, мы имеем в виду, что строка – это набор байт, представляющих некоторые кодовые обозначения, в соответствии с правилами кодировки UTF-8
.
Т. к. у нас есть символы вроде ł
, соответствующий коду 322
, нам нужно больше одного байта для их представления. Поэтому мы видим разницу при вычислении byte_size/1
в сравнении с результатом String.length/1
:
iex> string = "hełło"
"hełło"
iex> byte_size(string)
7
iex> String.length(string)
5
Так, byte_size/1
считает количество байт, необходимых для представления строки, а String.length/1
считает символы.
Обратите внимание. Tсли вы работаете под Виндоус, есть шанс, что ваш терминал не использует
UTF-8
по умолчанию. Вы можете изменить кодировку текущей сессии, выполнивchcp 65001
перед запускомIEx
(iex.bat
).
UTF-8
нужен один байт для представления символов h
, e
, и o
, но два байта для представления ł
. В Эликсире вы можете узнать код символа с помощью ?
:
iex> ?a
97
iex> ?ł
322
Вы можете также использовать функции из модуля String
для разделения строки на индивидуальные символы, каждый из которых будет строкой длины 1:
iex> String.codepoints("hełło")
["h", "e", "ł", "ł", "o"]
Вы увидите, что Эликсир имеет отличную поддержку работы со строками. Он также поддерживает многие операции Юникода. Фактически, Эликсир успешно проходит все тесты, показанные в статье «The string type is broken».
Однако, строки – это только часть истории. Раз строки являются бинарными, и мы использовали функцию is_binary/1
, Эликсир должен иметь более мощный тип, лежащий в основе строк. И он есть! Поговорим о бинарных данных.
Бинарные данные (и битовые строки)
В Эликсире вы можете определить бинарную последовательность, используя <<>>
:
iex> <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> byte_size(<<0, 1, 2, 3>>)
4
Бинарные данные – это последовательность байт. Эти байты могут быть записаны в любом порядке, даже если эта последовательность не будет корректной строкой:
iex> String.valid?(<<239, 191, 191>>)
false
Оператор конкатенации строк на самом деле оператор бинарной конкатенации:
iex> <<0, 1>> <> <<2, 3>>
<<0, 1, 2, 3>>
Часто используемый трюк в Эликсире – конкатенация нулевого байта <<0>>
к строке, чтобы увидеть её бинарное представление:
iex> "hełło" <> <<0>>
<<104, 101, 197, 130, 197, 130, 111, 0>>
Каждое число в бинарной последовательности должно помещаться в один байт, а значит, быть не больше 255. Существую модификаторы для хранения числе больше, чем 255 или конвертации кодовых обозначений в их UTF-8
представления:
iex> <<255>>
<<255>>
iex> <<256>> # обрезается
<<0>>
iex> <<256 :: size(16)>> # используется 16 бит (2 байта) для хранения числа
<<1, 0>>
iex> <<256 :: utf8>> # число в виде кодового обозначения
"Ā"
iex> <<256 :: utf8, 0>>
<<196, 128, 0>>
Если в байте 8 бит, что произойдёт, если мы укажем размер в 1 бит?
iex> <<1 :: size(1)>>
<<1::size(1)>>
iex> <<2 :: size(1)>> # обрезается
<<0::size(1)>>
iex> is_binary(<<1 :: size(1)>>)
false
iex> is_bitstring(<<1 :: size(1)>>)
true
iex> bit_size(<< 1 :: size(1)>>)
1
Значение больше не бинарное, но является битовой строкой – набором битов! Таким образом бинарная последовательность – это битовая строка, количество бит в которой делится на 8.
iex> is_binary(<<1 :: size(16)>>)
true
iex> is_binary(<<1 :: size(15)>>)
false
Мы можем также сравнивать по шаблону бинарные последовательности и битовые строки:
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>>
Тот же результат может быть достигнут с помощью оператора конкатенации <>
:
iex> "he" <> rest = "hello"
"hello"
iex> rest
"llo"
Полную справку о конструкторе <<>>
бинарных последовательностей / битовых строк можно найти в документации модулей Эликсира. На этом мы закончим обзор битовых строк, бинарных последовательностей и строк. Строка – это бинарная последовательность в кодировке UTF-8
, а бинарная последовательность – битовая строка, число бит в которой делится на 8. Хотя это показывает гибкость, которую даёт Эликсир для работы с битами и байтами, 99% времени вы будете работать с бинарными последовательностями, используя функции is_binary/1
и byte_size/1
.
Списки символов
Списки символов – не что иное, как списки из кодовых обозначений. Списки символов можно создать с помощью литералов в одиночных кавычках:
iex> 'hełło'
[104, 101, 322, 322, 111]
iex> is_list 'hełło'
true
iex> 'hello'
'hello'
iex> List.first('hello')
104
Вы можете увидеть, что список символов хранит не последовательность байт, а кодовые обозначения символов, указанных в одинарных кавычках (помните, что IEx
по умолчанию будет выводить только коды, если хотя бы один из них не входит в диапазон ASCII
). Таким образом двойные кавычки используются для строк (внутри это бинарная последовательность), одиночные – для списков символов (внутри список).
На практике, списки символов чаще всего используются для взаимодействия с Эрлангом, в частности со старыми библиотеками, которые не принимают бинарные последовательности в качестве аргументов. Вы можете конвертировать список символов в строку и обратно, используя функции: to_string/1
и to_charlist/1
:
iex> to_charlist "hełło"
[104, 101, 322, 322, 111]
iex> to_string 'hełło'
"hełło"
iex> to_string :hello
"hello"
iex> to_string 1
"1"
Обратите внимание, что эти функции полиморфичны. Они могут конвертировать в строки не только списки символов, но и числа, атомы и т. д.
На это всё с бинарными последовательностями, строками и списками символов. Самое время поговорить о структурах данных, основанных на парах ключ-значение.