二进制,字符串和字符串

在“Basic types”中,我们学习了字符串并使用了is_binary/1函数进行检查:

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

在本章中,我们将了解'like this'Elixir 中的二进制文件,它们如何与字符串关联,以及单引号值。

UTF-8 和 Unicode

一个字符串是一个 UTF-8 编码二进制。为了正确理解我们的意思,我们需要了解字节和代码点之间的区别。

Unicode 标准将代码点分配给我们知道的许多字符。例如,该字母a具有代码点,97而该字母ł具有代码点322。将字符串写入"hełło"磁盘时,我们需要将此代码点转换为字节。如果我们通过一个规则,说一个字节表示一个代码点,我们就不能写"hełło",因为它使用的代码点322ł,和一个字节只能从代表一个号码0255。但是,当然,由于您可以"hełło"在屏幕上阅读,因此必须以某种方式表示。这就是编码进来的地方。

当以字节表示代码点时,我们需要以某种方式对它们进行编码。Elixir选择了 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字符进行计数。

注意:如果你在 Windows 上运行,你的终端有可能默认不使用 UTF-8。您可以chcp 65001在输入iexiex.bat)前通过运行来更改当前会话的编码。

UTF-8 需要一个字节来表示字符heo,但有两个字节来表示ł。在Elixir中,你可以通过使用?以下代码获得角色的代码点:

iex> ?a
97
iex> ?ł
322

还可以使用的函数在String模块拆分在其个别字符的字符串,每一个为长度为1的字符串:

iex> String.codepoints("hełło")
["h", "e", "ł", "ł", "o"]

你会看到 Elixir 对使用字符串的工作有很好的支持。它也支持许多 Unicode 操作。事实上,Elixir 通过了文章“The string type is broken”展示的所有测试。

但是,字符串只是故事的一部分。如果一个字符串是一个二进制文件,并且我们已经使用了该is_binary/1函数,那么Elixir必须有一个赋予字符串赋权的基础类型。它确实!我们来谈谈二进制文件。

二进制文件(和位串)

在 Elixir 中,您可以使用<<>>以下方法定义二进制文件:

iex> <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> byte_size(<<0, 1, 2, 3>>)
4

二进制是一个字节序列。这些字节可以以任何方式进行组织,即使是不会使其成为有效字符串的序列:

iex> String.valid?(<<239, 191, 19>>)
false

字符串连接操作实际上是一个二进制连接操作符:

iex> <<0, 1>> <> <<2, 3>>
<<0, 1, 2, 3>>

Elixir 中的一个常见技巧是将空字节连接<<0>>到字符串以查看其内部二进制表示形式:

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

给二进制数的每个数字都表示一个字节,因此必须高达255.二进制数允许修饰符存储大于255的数字或将代码点转换为其 UTF-8 表示形式:

iex> <<255>>
<<255>>
iex> <<256>> # truncated
<<0>>
iex> <<256 :: size(16)>> # use 16 bits (2 bytes) to store the number
<<1, 0>>
iex> <<256 :: utf8>> # the number is a code point
"Ā"
iex> <<256 :: utf8, 0>>
<<196, 128, 0>>

如果一个字节有8位,如果我们传递1位的大小会发生什么?

iex> <<1 :: size(1)>>
<<1::size(1)>>
iex> <<2 :: size(1)>> # truncated
<<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位。如果我们想匹配未知大小的二进制文件,可以在模式结尾使用二进制修饰符:

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"

有关二进制/位字符串构造函数的完整参考<<>>可以在 Elixir 文档中找到。这就结束了我们对比特串,二进制文件和字符串的浏览。一个字符串是一个 UTF-8 编码二进制数,二进制是一个比特串,其比特数可以被8整除。虽然这表明了Elixir提供的位和字节处理的灵活性,但99%的时间你将使用二进制并使用is_binary/1byte_size/1功能。

Charlists

charlist 只不过是一个代码点列表。字符列表可以用单引号文字创建:

iex> 'hełło'
[104, 101, 322, 322, 111]
iex> is_list 'hełło'
true
iex> 'hello'
'hello'
iex> List.first('hello')
104

您可以看到,charlist 不包含字节,而是包含单引号之间字符的代码点(注意,如果任何整数超出 ASCII 范围,默认情况下 IEx 将只输出代码点)。所以,虽然双引号表示一个字符串(即二进制),单引号表示一个charlist(即列表)。

在实践中,charlists 主要用于与 Erlang 接口,特别是不接受二进制文件作为参数的旧库。您可以使用to_string/1to_charlist/1函数将charlist转换为字符串并返回:

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"

请注意,这些函数是多态的。它们不仅将 charlists 转换为字符串,还将整数转换为字符串,将原子转换为字符串等等。

通过二进制文件,字符串和 charlists,现在是讨论关键值数据结构的时候了。