Elixir 模式匹配似乎非常易于使用
结构化二进制数据。
是的。你可以感谢 erlang 的发明者。
根据文档,<<x :: size(y)>> 表示一个位串,
谁的十进制值为 x 并由一串位表示
y 长度。
让我们稍微简化一下:<<x :: size(y)>> 是插入到 y 位的整数 x。例子:
<<1 :: size(1)>> => 1
<<1 :: size(2)>> => 01
<<1 :: size(3)>> => 001
<<2 :: size(3)>> => 010
<<2 :: size(4)>> => 0010
binary 类型中的位数可以被 8 整除,因此二进制类型具有整数字节数(1 字节 = 8 位)。 bitstring 中的位数不能被 8 整除。这就是 binary 类型和 bitstring 类型之间的区别。
我理解 > 表示二进制对象 x。逻辑上对我来说,
看起来这类似于执行:[head |尾] = 列表
在 List 上,获取第一个元素,然后将其余元素作为
名为 tail 的新列表。
是的:
defmodule A do
def show_list([]), do: :ok
def show_list([head|tail]) do
IO.puts head
show_list(tail)
end
def show_binary(<<>>), do: :ok
def show_binary(<<char::binary-size(1), rest::binary>>) do
IO.puts char
show_binary(rest)
end
end
在 iex 中:
iex(6)> A.show_list(["a", "b", "c"])
a
b
c
:ok
iex(7)> "abc" = <<"abc">> = <<"a", "b", "c">> = <<97, 98, 99>>
"abc"
iex(9)> A.show_binary(<<97, 98, 99>>)
a
b
c
:ok
或者您可以将二进制中的整数解释为普通的旧整数:
def show(<<>>), do: :ok
def show(<<ascii_code::integer-size(8), rest::binary>>) do
IO.puts ascii_code
show(rest)
end
在 iex 中:
iex(6)> A.show(<<97, 98, 99>>)
97
98
99
:ok
utf8 类型非常有用,因为它会抓取尽可能多的字节来获取整个 utf8 字符:
def show(<<>>), do: :ok
def show(<<char::utf8, rest::binary>>) do
IO.puts char
show(rest)
end
在 iex 中:
iex(8)> A.show("ۑ")
8364
235
:ok
如您所见,uft8 类型返回字符的 unicode 代码点。将字符作为字符串/二进制获取:
def show(<<>>), do: :ok
def show(<<codepoint::utf8, rest::binary>>) do
IO.puts <<codepoint::utf8>>
show(rest)
end
您获取代码点(一个整数)并使用它来创建二进制/字符串<<codepoint::utf8>>。
在 iex 中:
iex(1)> A.show("ۑ")
€
ë
:ok
但是,您不能为 utf8 类型指定大小,因此如果要读取多个 utf8 字符,则必须指定多个段。
当然,段 rest::binary,即没有指定大小的 binary 类型,非常有用。它只能出现在模式的末尾,rest::binary 就像贪婪的正则表达式:(.*)。 rest::bitstring 也是如此。
尽管 elixir 文档在任何地方都没有提到它,但片段中的 total number of bits 是其中之一:
| | |
v v v
<< 1::size(8), 1::size(16), 1::size(1) >>
实际上是unit * size,其中每种类型都有一个默认的unit。段的默认类型是integer,因此上面每个段的类型默认为integer。整数的默认unit 为1 位,因此第一段的总位数为:8 * 1 bit = 8 bits。 binary 类型的默认 unit 为 8 位,因此段如下:
<< char::binary-size(6)>>
总大小为6 * 8 bits = 48 bits。等效地,size(6) 只是字节数。您可以指定unit,就像您可以指定size,例如<<1::integer-size(2)-unit(3)>>。该段的总位大小为:2 * 3 bits = 6 bits。
但是,我不熟悉语法
看看这个:
def bitstr2bits(bitstr) do
for <<bit::integer-size(1) <- bitstr>>, do: bit
end
在 iex 中:
iex(17)> A.bitstr2bits <<1::integer-size(2), 2::integer-size(2)>>
[0, 1, 1, 0]
等价:
iex(3)> A.bitstr2bits(<<0b01::integer-size(2), 0b10::integer-size(2)>>)
[0, 1, 1, 0]
Elixir 倾向于使用库函数抽象出递归,因此通常您不必像在链接中那样提出自己的递归定义。但是,该链接显示了标准的基本递归技巧之一:将 accumulator 添加到函数调用以收集您希望函数返回的结果。该函数也可以这样写:
def bitstr2bits(<<>>), do: []
def bitstr2bits(<<bit::integer-size(1), rest::bitstring>>) do
[bit | bitstr2bits(rest)]
end
链接处的累加器函数是尾递归,这意味着它占用了恒定(少量)的内存——无论需要多少次递归函数调用来逐步遍历位串。一千万位的位串?需要 1000 万次递归函数调用?那只需要少量的内存。在过去,我发布的替代定义可能会使您的程序崩溃,因为它会为每个递归函数调用占用越来越多的内存,并且如果位串足够长,则所需的内存量会太大,您会获取 stackoverflow 并且您的程序会崩溃。但是,erlang 已经优化掉了非尾递归的递归函数的缺点。