Erlang中的二进制位串(2)

了解完 Erlang 的基本特性之后,就进入了基本语法的学习,但这部分着实让我费了点力气,因为和之前接触过的编程语言(C,C++,JAVA,C#)太不一样了,有很多新的理念,这儿我记录一下 Erlang 中一个很有特色的语言元素——二进制位串。

二进制串就是无符号8位字节序列,用于存放和处理数据块(通常是读自文件或通过某网络协议接收到的数据)。位串则是广义的二进制串,其长度不必是8的整数倍,如一个半字节共12位。但是两者表面差异不大,只是如今可以借助位串完成一些以往无法完成的轻巧任务。由于语法相同,现在已经没什么人再用位串这个说法了,除非是有必要强调长度方面的灵活性。二进制位串的基本语法如下:

<<0,1, 2, ..., 255>>

也就是一个包含在<<...>>内的逗号分隔的整数序列,整数取值范围为0~255。<<>>表示一个空二进制串,我们还可以用字符串构造二进制串,比如

<<"hello", 32, "dude">>

这与直接输入字符串中相应字符的8位编码(ASCII)效果相同。所以这种写法仅限于8位字符,不过在处理基于文本的协议时常常会用到它。上面这两个示例只是简单展示了如何创建长度为 8 的整数倍的二进制串。接下来介绍更为高级的构造二进制位串的方法。

构造位串

通过比特位语法我们可以随心所欲地构造指定尺寸和布局的二进制串;反之,它也可以用于匹配和抽取位串中指定的区段(例如从文件或者套接字中读取的二进制数据)。位串可以写作

<<Segment1, ..., SegmentN>>
其中双小于号和双大于号之间可以包含零个或者多个区段指示符(segment specifier)。位串以比特位为单位的整体长度,就是各区段长度的总和。区段指示符可以为以下形式之一:

Data
Data:Size
Data/TypeSpecifiers
Data:Size/TypeSpecifiers
Data 必须是整数、浮点数或者另一个位串。我们可以将区段长度指定为单位长度的某整数倍,我们还可以指定区段的类型,该类型决定了如何解读 Data 以及如何对它进行编解码。例如这样的一个简单的二进制串<<1,2,3>>,它有三个区段,每个区段的数据都是一个整数,区段既没有尺寸也没有类型指示符。这个例子中,类型默认为integer,而integer的默认尺寸为1。integer 类型的单位是 8 比特位,因此区段被编码为 8 位无符号字节。与此类似,<<"abc">>是<<$a,$b,$c>>的缩写——也即是一个8位整数字符编码(Latin-1)序列。整数的位数如果超出了区段的最大空间,就会被截断,所以<<254,255,256,257>>就变成了<<254,255,0,1>>。

区段的类型由我们指定,不取决于 Data 自身的类型。举个例子,我们无法像这样串接两个位串:

B1 = <<1,2>>,
B2 = <<3,4>>,
<<B1,B2>>.
因为默认情况下B1和B2是被当作整数的。但是如果指定B1和B2是位串,就可以

<<B1/bits,B2/bits>>
这样便能得到期望的

<<1,2,3,4>>
我们可以通过 TypeSpecifiers 部分(位于 / 后面)来控制区段编解码的细节。它由一个或多个短杠(-)分割的原子组成,如 integer-unsigned-big。原子出现的次序不重要,当前可以使用的指示符的集合如下:

  • intger、float、binary、bytes、bitstring、bits、utf8、utf16、utf32
  • signed、unsigned
  • big、little、native
这些指示符可以按照多重方式组合,但是上述每组只能出现一个。bits 是 bitstring 的别名,bytes 是 binary 的别名。对于 integer、float和bitstring类型,尺寸的单位是 1 比特位,binary 的单位则是8比特位(一字节)。

比特位语法中的模式匹配

我们可以用比特位语法来分解位串中的数据。相较于手工完成各种位移和掩码运算,用比特位语法来解析各种怪异的文件格式和协议数据更加方便。下面展示如何用函数字句中的模式来解析 IP 报文首部的内容:
ipv4(<<Version:4, IHL:4, TOS:8, TotalLength:16, Identification:16, Flags:3, FragOffset:13,TimeToLive:8, Protocol:8, Checksum:16, SourceAddress:32, DestinationAddress:32,OptionAndPadding:((IHL-5)*32)/bits, RemainingData/bytes >>) when Version =:= 4 -> ...
只要传入的报文的尺寸足够进行匹配,且 Version 字段为 4,报文便会自动解析为相应的变量,大部分变量都被解析为整数,只有 OptionAndPadding (一个长度取决于先前解析出的 IHL 字段的位串)和 RemainingData 段除外,其中后者包含报文首部之后的所有数据。从一个二进制串中凑趣另一个二进制串并不涉及数据复制,因此这种运算的成本很低。

位串速构
存在于很多函数式编程语言中的列表速构的思想,也被扩展到了 Erlang 的比特位语法中。位串速构恰似列表速构,只是[...]被换成了<<...>>。以一个小整数列表为例,所有整数都在0和7之间,你可以按每个数3比特位将它们打包成位串,如下:

<< <<X:3>> ||  X <- [1,2,3,4,5,6,7] >>
shell 会将上式得到的位串打印成<<41,203,23:5>>。请注意末尾的23:5——位串的总长度为8+8+5=21比特位,考虑到输入列表包含7个元素,这个结果是正确的。同样我们可以用位串速构来将这样一个位串解码。但是解码我们需要将生成器中的<-换成<=,表示从位串中提取内容,而<-只能从列表中选取元素:

<< <<X:8>> || <<X:3>> <= <<41,203,23:5>> >>

得到的二进制串是<<1,2,3,4,5,6,7>>,由此可见,此前我们确实成功地将 3 比特位整数格式转换成了 8 比特位整数格式。但若我们希望得到的结果是列表而非位串该怎么办呢?把位串生成器用到列表速构里就可以了!

[X ||  <<X:3>>  <=  <<41,203,23:5>>]
产生的对应的列表为[1,2,3,4,5,6,7]。


阅读更多
换一批

没有更多推荐了,返回首页