第一次看到Joe Armstong的《Erlang 程序设计》里面对Binary的描述时,觉得这个东西好复杂,语法这么奇特(我觉得是Erlang语法中最不好懂的部分);
然后在项目中:Binary的处理都在网络包处理层,基本不会有改动,所以从此以后就再也没有去深看Binary。
但是看cowboy最新版本的优化说:
https://github.com/ninenines/cowboy
Cowboy aims to provide a complete HTTP stack in a small code base. It is optimized for low latency and low memory usage, in part because it uses binary strings.
就又非(gu)常(qi)好(yong)奇(qi)地想了解下这个神奇的Binary.[可是网络上的关于Binary的资料真的是少得可怜...]
但是:不过还是淘到了几篇非常好的文章:
- ErlangVM 是怎么实现Binary数据类型的,实现原理从宏观到细节,值得反复细读:http://www.cnblogs.com/zhengsyao/p/erlang_eterm_implementation_5_binary.html
- 这个是从应用层上去具体使用上去解释为什么Binary会非常高效且内存占用比List少:http://cryolite.iteye.com/blog/1547252
- 1 中提到的官方效率指南:http://www.erlang.org/doc/efficiency_guide/binaryhandling.html
- 如果要处理Binary最好自己写模式匹配或使用binary.erl里面的函数:http://stackoverflow.com/questions/21779394/erlang-high-memory-usage-for-processing-list-of-binary-parts
理解了上面的内容后,就会认识到Binary的强大,但是对Binary的语法还是心存恐惧:
其实如果一个事物不熟悉或太自由,大部分人都首先觉得“哇,好牛逼”,渐渐地恰恰由于这过多的自由(太灵活),有时会感觉到自身的驾驭能力不足,多少会有点害怕,进而就不想去理解它【抱着一种反正可以用其它方法取代的心态】
下面,我们就通过把Binary和List(这个不熟悉就说不过去了)对比来看Binary的语法,不要因为恐惧错过美好的东西:
引用: http://user.it.uu.se/~pergu/papers/erlang05.pdf
首先我们知道List的语法是最简单,明了的。其实Binary的目的最终也是想把Binary的语法和List保持一致:
1.Binary怎么节省的空间?
keep_0XX([{0,B2,B3}|Rest]) -> [{0,B2,B3}|keep_0XX(Rest)]; keep_0XX([{1,_,_}|Rest]) -> keep_0XX(Rest); keep_0XX([]) -> [].
或者使用下面的列表解析方式:
keep_0XX(List) -> [{0,B2,B3} || {0,B2,B3} <- List].
上面这个函数看上去简洁优雅,简直可以说是完美,但是还有2个问题:
1.1 这个三元tuple非常浪费空间,如果使用<<B1/Size1,B2/Size2,B3/Size2>>来从bit级别去理解你的需要,想分配多少就直接给多个,这样才是极致;
1.2 输入的不确定性:可能来自于网络,或能来自于文件中,这时,我们还要把得到的数据转化为一个3元tuple的List,为什么不能一步到位?
所以:这里使用Binary[网络中的数据包大部分都是Binary,除了文本的http]会更好:
keep_0XX(Bin) -> [ <<0:1,B:2>> || <<0:1,B:2>> <= Bin].
2.Binary语法:温故而知新,多想想它为什么要这么规定[一切都是为了网络数据]?
<<Segment1,Segment2,...,Senmentn>>
每个Segment都是现面这种方式
Value:Size/TypeSpecifierList
2.1.Value可以是任意的Erlang Term,绑定的变量,不绑定的变量,不关心的"_";
2.2 Size 可以是正整数或绑定为正整数的变量(不能是不绑定变量),但总的Size加起来一定是8的倍数,因为二进制没有办法表达一个非8倍数长度的比特串;
Integer默认为8,Float默认为64,其它类型在模式匹配时必须指定Size.
2.3 TypeSpecifierList 由End-Sign-Type-Unit的列表:每一个前置项可以忽略,没有要求。
2.3.1 Type可以是: integer | float | binary | bytes | bitstring | bits | utf8 | utf16 | utf32 如果不指定就默认为Integer
2.3.2 Signedness: signed | unsigned 只有当Type为Integer时才会有用,默认为‘unsigned‘
2.3.3 Endianness: big | little | native 指定计算机系统的字节序,native是运行时决定的字节序,依赖于CPU,默认为big,唯一用到这项的情形就是处理整数和二进制数据之间的封包和解包工作。
例如你在big上看到的是<<0,0,0,72>> 在little上看到的是<<72,0,0,0>>.
2.3.4 Unit: unit:Integer 1~255 整个区块长度为Size*Unit bit整个区块的长度必须>=0且 整除8
Unit默认值由Type决定:Type=integer或float时为1,Type=binary则为8
Joe大爷说:如果你还是对比特语法感觉不适应,最好的办法就是在shell中尝试你需要的模式直接得到真确的值,然后把它们复制粘贴到程序中就行啦。他就是这么做的....
给一些binary默认时的情况给你测试下下:
Segment | Default expansion |
X | X:8/integer-unit:1 |
X/float | X:64/float-unit:1 |
X/binary | X:all/binary |
X:size/binary | X:Size/binary-unit:8 |
3.Binary模式匹配:
3.1 示例1:最基本的:
Binary = <<10, 11, 12>>, <<A:8, B/binary>> = Binary. A=10,B=<<11,12>>.
3.2 示例2 Size并不需要事先绑定值,通常的做法是:
<<Sz:8/integer, Vsn:Sz/integer, Msg/binary>> = <<16,2,154,42>>. Sz = 16,Vsn=666,Msg=<<42>>.
先从前面得到头,再在后面的匹配中使用
3.3.
case Binary of <<42:8/integer, X/binary>> -> handle bin(X); <<Sz:8, V:Sz/integer, X/binary>> when Sz > 16 -> handle int bin(V, X); << :8, X:16/integer, Y:8/integer>> -> handle int int(X, Y) end.
Binary | Matching of X |
<<42,14,15>> | <<14,15>> |
<<24,1,2,3,10,20>> | <<10,20>> |
<<12,1,2,20>> | 258 |
<<0,255>> | failure |
4.一些关于binary的BIF
4.1 binary_to_list(Bin) 这个函数只能处理size为8的整数的Bin[你可以试下:binary_to_list(<<1:21>>)).];
4.2 size(Bin)是返回存储Bin实际的大小空间,不是分配给他的,如果你要查看分配给他的,就用bit_size(Bin).
5.Binary 的binary解析【相对于List的列表解析】
不要忘记了Binary的语法的终极目标,做得和List一样好用!
5.1 把Binary转换为List:【只需要<-变成了<= 】
1> [ X || <<X>> <= <<1,2,3,4,5>>, X rem 2 == 0]. [2,4]
5.2 如果你只是想把不是binary处理后变成一个binary就不用使用 <=
2> << <<R:8, G:8, B:8>> || {R,G,B} <- [{213,45,132},{64,76,32},{76,0,0},{234,32,15}] >>. <<213,45,132,64,76,32,76,0,0,234,32,15>>
马上就要开学啦,祝高中生们在逛街的时候偶遇到班主任~~~哈哈~~~