Elixir入门教程-二进制数据,字符串 和 字符列表

  1. UTF-8和Unicode
  2. 二进制数据(和位串)
  3. 字符列表

在“基本类型”那一章,我们学到了字符串以及使用 is_binary/1 函数来检查它:

1
2
3
4
iex> string = "hello"
"hello"
iex> is_binary(string)
true

在本章,我们将理解二进制数据是什么;它们如何和字符串联系在一起;在Elixir里,被单引号括起来的值,比如 ‘like this’,是什么。

UTF-8和Unicode

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

Unicode标准给我们所知的许多字符赋予了代码点。比如,字母 a 有代码点 97,而字母 ł 有代码点322。当将字符串 “hełło” 写到磁盘上时,我们需要将它的代码点转换为若干字节。如果我们采用的规则是,一个字节表示一个代码点,那么我们将无法写字符串 “hełło” ,因为它使用了代码点322来表示字母 ł ,而一个字节只能表示 0 到 255的数字。理所当然地,你要正确地在屏幕上读出字符串 “hełło” ,它必须被以某种方式表达出来。这就是编码应用之处了。

用字节的方式来表达代码点的时候,我们需要以某种方式编码它们。Elixir选择UTF-8编码作为它主要的和默认的编码。当我们说一个字符串是一个UTF-8编码的二进制数据,我们的意思是一个字符串是以一种方式,比如指定的UTF-8编码,来组织的表达某些代码点的一块二进制数据。

因为我们有字母,像 ł 被赋予代码点322,所以我们的确需要多于一个字节来表达它们。这就是为什么我们看到的 byte_size/1 和 String.length/1 之间有区别:

1
2
3
4
5
6
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编码。你可以在运行iex(iex.bat)前通过运行 chcp 65001 来改变你当前会话的编码。

UTF-8需要一个字节来表达字符 h,e 和 o ,但是需要两个字节表达字符 ł 。在Elixir里,你可以用 ? 来获得一个字符的代码点:

1
2
3
4
iex> ?a
97
iex> ?ł
322

你也可以用String模块里的函数将一个字符串分割为独立字符串组成的列表,其中每一个字符串是长度为一。

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

你将看到Elixir已经非常好地支持字符串操作。它也支持许多Unicode操作。实际上,Elixir通过了“The string type is broken”里的所有测试。

然后,字符串只是本文的一部分内容。如果一个字符串是一个二进制数据,并且我们也对它使用了is_binary/1函数,那么Elixir必须有一个底层类型来支持字符串。它的确是这么做的!下面我们来谈谈二进制数据。

二进制数据(和位串)

在Elixir里,你可以用<<>>来定义一个二进制数据。

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

一个二进制数据是一个字节的序列。这些字节可以用任何方式来组织,甚至这些字节序列不是有效的字符串:

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

字符串的串联操作实际上是二进制数据的串联操作:

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

在Elixir里的一个通用技巧是将空字节<<0>>串联在一个字符串后以便看看它内部的二进制表示:

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

赋给一个二进制数据的每一个数字都表示一个字节,由此它的值最大是255。二进制数据允许给定修饰语来存储大于255的数字,或者转换一个代码点为它的UTF-8编码表示:

1
2
3
4
5
6
7
8
9
10
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
2
3
4
5
6
7
8
9
10
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整除。

1
2
3
4
iex> is_binary(<<1 :: size(16)>>)
true
iex> is_binary(<<1 :: size(15)>>)
false

我们也可以在二进制数据或位串上进行模式匹配:

1
2
3
4
5
6
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位。如果我们想要匹配一个未知大小的二进制数据,在模式的尾部通过使用二进制数据修饰语是可能做到的:

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

相似的结果可以用字符串串联符<>来实现:

1
2
3
4
iex> "he" <> rest = "hello"
"hello"
iex> rest
"llo"

关于二进制数据或位串的构造器<<>>的完整指导可以在Elixir官方文档里找到。到此,我们结束了位串,二进制数据和字符串之旅。字符串是UTF-8编码的二进制数据;二进制数据是位串,它的位的数量被8整除。虽然这表明Elixir提供了处理位和字节的灵活性,不过你的99%的时间将用来处理二进制数据,并且使用 is_binary/1 和 byte_size/1 函数。

字符列表

一个字符列表就是一个代码点列表。字符列表可以用单引号括起来的字符字面值创建:

1
2
3
4
5
6
7
8
iex> 'hełło'
[104, 101, 322, 322, 111]
iex> is_list 'hełło'
true
iex> 'hello'
'hello'
iex> List.first('hello')
104

你可以看到,不是包含字节,一个字符列表包含单引号之间的字符的代码点(注意:默认情况下如果任何整数值在ASCII范围外,则IEx将只是输出代码点)。那么,双引号表示字符串(即二进制数据),单引号表示字符列表(即列表)。

在实践中,字符列表主要用于与Erlang交互的时候,特别是不接受二进制数据作为参数的旧库。你可以用 to_charlist/1 和 to_string/1 函数来转换字符列表为字符串或者反之将字符串转换为字符列表:

1
2
3
4
5
6
7
8
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"

注意:这些函数是多态的。它们不仅可以转换字符列表为字符串,也可以转换整数为字符串,转换原子为字符串,等等。

介绍完二进制数据,字符串和字符列表,接下来是时候讲讲键值对数据结构了。

原文链接: http://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html