逗号分隔的二进制参数?- 长生不老药

Hap*_*ing 5 binary elixir

这个月我一直在学习 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)

7st*_*tud 4

\n

Elixir 模式匹配对于结构化二进制数据来说似乎非常容易使用。

\n
\n\n

是的。你可以感谢 erlang 的发明者。

\n\n
\n

根据文档,<<x :: size(y)>>表示一个位串,\n 其十进制值为 x,并由长度为 \n 的位串表示。

\n
\n\n

让我们简单一点:<<x :: size(y)>> 整数 x 是否插入到 y 位中。例子:

\n\n
<<1 :: size(1)>>  => 1\n<<1 :: size(2)>>  => 01\n<<1 :: size(3)>>  => 001\n<<2 :: size(3)>>  => 010\n<<2 :: size(4)>>  => 0010\n
Run 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\n

是的:

\n\n
defmodule 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\n
Run Code Online (Sandbox Code Playgroud)\n\n

在iex中:

\n\n
iex(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\n
Run 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\n
Run Code Online (Sandbox Code Playgroud)\n\n

在iex中:

\n\n
iex(6)> A.show(<<97, 98, 99>>)            \n97\n98\n99\n:ok\n
Run Code Online (Sandbox Code Playgroud)\n\n

utf8类型非常有用,因为它将获取获取整个 utf8 字符所需的尽可能多的字节:

\n\n
  def show(<<>>), do: :ok\n\n  def show(<<char::utf8, rest::binary>>) do\n    IO.puts char\n    show(rest)\n  end\n
Run Code Online (Sandbox Code Playgroud)\n\n

在iex中:

\n\n
iex(8)> A.show("\xe2\x82\xac\xc3\xab")\n8364\n235\n:ok\n
Run Code Online (Sandbox Code Playgroud)\n\n

如您所见,该uft8类型返回字符的 unicode 代码点。要将字符获取为字符串/二进制:

\n\n
  def show(<<>>), do: :ok\n  def show(<<codepoint::utf8, rest::binary>>) do\n    IO.puts <<codepoint::utf8>>\n    show(rest)\n  end\n
Run Code Online (Sandbox Code Playgroud)\n\n

您获取代码点(整数)并使用它来创建二进制/字符串<<codepoint::utf8>>

\n\n

在iex中:

\n\n
iex(1)> A.show("\xe2\x82\xac\xc3\xab")\n\xe2\x82\xac                                                          \n\xc3\xab\n:ok\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是,您无法指定utf8类型的大小,因此如果您想读取多个 utf8 字符,则必须指定多个段。

\n\n

当然,段rest::binary(即binary没有指定大小的类型)非常有用。它只能出现在模式的末尾,rest::binary就像贪婪的正则表达式:(.*)。也同样如此rest::bitstring

\n\n

尽管 Elixir 文档没有在任何地方提到它,但total number of bits在段中,段是其中之一:

\n\n
     |              |          |    \n     v              v          v\n<< 1::size(8), 1::size(16), 1::size(1) >>\n
Run Code Online (Sandbox Code Playgroud)\n\n

实际上unit * size,每种类型都有一个默认值unit。段的默认类型是integer,因此上面每个段的类型默认为integer。整数默认unit为 1 位,因此第一段中的总位数为:8 * 1 bit = 8 bitsunit该类型的默认值binary是 8 位,因此段如下:

\n\n
<< char::binary-size(6)>>\n
Run 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

看一下这个:

\n\n
  def bitstr2bits(bitstr) do\n    for <<bit::integer-size(1) <- bitstr>>, do: bit\n  end\n
Run Code Online (Sandbox Code Playgroud)\n\n

在iex中:

\n\n
iex(17)> A.bitstr2bits <<1::integer-size(2), 2::integer-size(2)>>   \n[0, 1, 1, 0]\n
Run Code Online (Sandbox Code Playgroud)\n\n

等效地:

\n\n
iex(3)> A.bitstr2bits(<<0b01::integer-size(2), 0b10::integer-size(2)>>)\n[0, 1, 1, 0]\n
Run Code Online (Sandbox Code Playgroud)\n\n

Elixir 倾向于使用库函数抽象出递归,因此通常您不必像链接中那样提出自己的递归定义。但是,该链接显示了标准的基本递归技巧之一:向函数调用添加累加器以收集您希望函数返回的结果。该函数也可以这样写:

\n\n
  def bitstr2bits(<<>>), do: [] \n  def bitstr2bits(<<bit::integer-size(1), rest::bitstring>>) do\n    [bit | bitstr2bits(rest)]\n  end\n
Run Code Online (Sandbox Code Playgroud)\n\n

链接处的累加器函数是尾递归的,这意味着它占用恒定(少量)的内存——无论需要多少次递归函数调用来逐步遍历位串。具有 1000 万位的位串?需要 1000 万次递归函数调用?这只需要少量的内存。在过去,我发布的替代定义可能会使您的程序崩溃,因为它会为每个递归函数调用占用越来越多的内存,并且如果位串足够长,则所需的内存量将太大,并且您会得到stackoverflow,你的程序就会崩溃。然而,erlang 已经优化了非尾递归的递归函数的缺点。

\n