这个月我一直在学习 elixir,当时我想将二进制对象转换为位列表,以进行模式匹配。
我的研究使我来到这里,找到了一篇展示了这样做的方法的文章。但是,我并不完全理解传递给extract函数的参数之一。
我可以复制并粘贴代码,但我想了解这里的幕后情况。
论据是这样的:<<b :: size(1), bits :: bitstring>>。
我的理解
我知道这<< x >>表示一个二进制对象x。在我看来,这似乎类似于执行:[head | tail] = list在 List 上获取第一个元素,然后将其余元素作为名为 tail 的新列表。
我不明白的
但是,我对语法不熟悉,我从未::在 elixir 中见过,也从未见过用逗号分隔的二进制对象:,。我也没有看到size(x)在 Elixir 中使用过,也从未遇到过bitstring.
底线
如果有人可以准确解释此参数的语法是如何分解的,或者将我指向一个资源,我将不胜感激。
为方便起见,该文章中的代码:
defmodule Bits do
# this is the public api which allows you to pass any binary representation
def extract(str) when is_binary(str) do
extract(str, [])
end
# this function does the heavy lifting by matching the input binary to
# a single bit and sends the rest of the bits recursively back to itself
defp extract(<<b :: size(1), bits :: bitstring>>, acc) when is_bitstring(bits) do
extract(bits, [b | acc])
end
# this is the terminal condition when we don't have anything more to extract
defp extract(<<>>, acc), do: acc |> Enum.reverse
end
IO.inspect Bits.extract("!!") # => [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1]
IO.inspect Bits.extract(<< 99 >>) #=> [0, 1, 1, 0, 0, 0, 1, 1]
Run Code Online (Sandbox Code Playgroud)
\n\n\nElixir 模式匹配对于结构化二进制数据来说似乎非常容易使用。
\n
是的。你可以感谢 erlang 的发明者。
\n\n\n\n\n根据文档,
\n<<x :: size(y)>>表示一个位串,\n 其十进制值为 x,并由长度为 \n 的位串表示。
让我们简单一点:<<x :: size(y)>> 整数 x 是否插入到 y 位中。例子:
<<1 :: size(1)>> => 1\n<<1 :: size(2)>> => 01\n<<1 :: size(3)>> => 001\n<<2 :: size(3)>> => 010\n<<2 :: size(4)>> => 0010\nRun Code Online (Sandbox Code Playgroud)\n\n该类型中的位数binary可以被 8 整除,因此二进制类型具有整数个字节(1 字节 = 8 位)。a中的位数不能被8整除。这就是类型和类型bitstring的区别。binarybitstring
\n\n\n我知道 << x >> 表示二进制对象 x。从逻辑上来说,\n 这看起来类似于执行: [head | tail] = list\n 在一个 List 上,获取第一个元素,然后将剩余的元素作为一个名为 tail 的新列表。
\n
是的:
\n\ndefmodule A do\n\n def show_list([]), do: :ok\n def show_list([head|tail]) do\n IO.puts head\n show_list(tail)\n end\n\n def show_binary(<<>>), do: :ok\n def show_binary(<<char::binary-size(1), rest::binary>>) do\n IO.puts char\n show_binary(rest)\n end\n\nend\nRun Code Online (Sandbox Code Playgroud)\n\n在iex中:
\n\niex(6)> A.show_list(["a", "b", "c"]) \na\nb\nc\n:ok\n\niex(7)> "abc" = <<"abc">> = <<"a", "b", "c">> = <<97, 98, 99>>\n"abc"\n\niex(9)> A.show_binary(<<97, 98, 99>>) \na\nb\nc\n:ok\nRun Code Online (Sandbox Code Playgroud)\n\n或者您可以将二进制中的整数解释为普通的旧整数:
\n\n def show(<<>>), do: :ok\n\n def show(<<ascii_code::integer-size(8), rest::binary>>) do\n IO.puts ascii_code\n show(rest)\n end\nRun Code Online (Sandbox Code Playgroud)\n\n在iex中:
\n\niex(6)> A.show(<<97, 98, 99>>) \n97\n98\n99\n:ok\nRun Code Online (Sandbox Code Playgroud)\n\n该utf8类型非常有用,因为它将获取获取整个 utf8 字符所需的尽可能多的字节:
def show(<<>>), do: :ok\n\n def show(<<char::utf8, rest::binary>>) do\n IO.puts char\n show(rest)\n end\nRun Code Online (Sandbox Code Playgroud)\n\n在iex中:
\n\niex(8)> A.show("\xe2\x82\xac\xc3\xab")\n8364\n235\n:ok\nRun Code Online (Sandbox Code Playgroud)\n\n如您所见,该uft8类型返回字符的 unicode 代码点。要将字符获取为字符串/二进制:
def show(<<>>), do: :ok\n def show(<<codepoint::utf8, rest::binary>>) do\n IO.puts <<codepoint::utf8>>\n show(rest)\n end\nRun Code Online (Sandbox Code Playgroud)\n\n您获取代码点(整数)并使用它来创建二进制/字符串<<codepoint::utf8>>。
在iex中:
\n\niex(1)> A.show("\xe2\x82\xac\xc3\xab")\n\xe2\x82\xac \n\xc3\xab\n:ok\nRun Code Online (Sandbox Code Playgroud)\n\n但是,您无法指定utf8类型的大小,因此如果您想读取多个 utf8 字符,则必须指定多个段。
当然,段rest::binary(即binary没有指定大小的类型)非常有用。它只能出现在模式的末尾,rest::binary就像贪婪的正则表达式:(.*)。也同样如此rest::bitstring。
尽管 Elixir 文档没有在任何地方提到它,但total number of bits在段中,段是其中之一:
| | | \n v v v\n<< 1::size(8), 1::size(16), 1::size(1) >>\nRun Code Online (Sandbox Code Playgroud)\n\n实际上unit * size,每种类型都有一个默认值unit。段的默认类型是integer,因此上面每个段的类型默认为integer。整数默认unit为 1 位,因此第一段中的总位数为:8 * 1 bit = 8 bits。unit该类型的默认值binary是 8 位,因此段如下:
<< char::binary-size(6)>>\nRun Code Online (Sandbox Code Playgroud)\n\n总大小为6 * 8 bits = 48 bits. 等效地,size(6)只是字节数。unit您可以像指定 一样指定size,例如<<1::integer-size(2)-unit(3)>>。该段的总位大小为:2 * 3 bits = 6 bits。
\n\n\n但是,我不熟悉语法
\n
看一下这个:
\n\n def bitstr2bits(bitstr) do\n for <<bit::integer-size(1) <- bitstr>>, do: bit\n end\nRun Code Online (Sandbox Code Playgroud)\n\n在iex中:
\n\niex(17)> A.bitstr2bits <<1::integer-size(2), 2::integer-size(2)>> \n[0, 1, 1, 0]\nRun Code Online (Sandbox Code Playgroud)\n\n等效地:
\n\niex(3)> A.bitstr2bits(<<0b01::integer-size(2), 0b10::integer-size(2)>>)\n[0, 1, 1, 0]\nRun Code Online (Sandbox Code Playgroud)\n\nElixir 倾向于使用库函数抽象出递归,因此通常您不必像链接中那样提出自己的递归定义。但是,该链接显示了标准的基本递归技巧之一:向函数调用添加累加器以收集您希望函数返回的结果。该函数也可以这样写:
\n\n def bitstr2bits(<<>>), do: [] \n def bitstr2bits(<<bit::integer-size(1), rest::bitstring>>) do\n [bit | bitstr2bits(rest)]\n end\nRun Code Online (Sandbox Code Playgroud)\n\n链接处的累加器函数是尾递归的,这意味着它占用恒定(少量)的内存——无论需要多少次递归函数调用来逐步遍历位串。具有 1000 万位的位串?需要 1000 万次递归函数调用?这只需要少量的内存。在过去,我发布的替代定义可能会使您的程序崩溃,因为它会为每个递归函数调用占用越来越多的内存,并且如果位串足够长,则所需的内存量将太大,并且您会得到stackoverflow,你的程序就会崩溃。然而,erlang 已经优化了非尾递归的递归函数的缺点。
\n