文本文件 / 二进制文件 / 二进制bit流
计算机在存储或传输数据时都是以 bit 流的形式(二进制),文本文件和二进制文件的主要区别就是在于文本文件是有字符集的,ascii/utf8/utf16
等,读取时会将二进制流解码成对应的字符集字符。而二进制文件则简单的将数据作为二进制流处理,使用文本编辑器打开时,ascii
解码,1byte 1byte
的处理并,有的落在ascii
可打印字符中的就显示,没有落在其中的就是我们看到的乱码了。
例如
存储
ascii 文本数据 hello 存储的二进制流如下
存储的是每个字符的 ASCII 码
01101000 01100101 01101100 01101100 01101111
读取
按 ascii 文本文件读取
会依次读取 1byte 然后输出 ascii 码表对应的字符
为了便于比较我们这里读取 4byte 的数据
01101000 01100101 01101100 01101100
h--------e--------l--------l-------
按二进制流文件读取
可以读取 4byte 作为 uint 解析
二进制:01101000 01100101 01101100 01101100
十进制:|------------1751477356-----------|
可以读取 2byte 作为 usint 解析
二进制:01101000 01100101 01101100 01101100
十进制:|-----26725-----| |-----27756-----|
所以,文本文件和二进制文件的不同之处是解析方式不同,文本文件需要按照自身字符集,截取相应的字节长度读取,ascii 1byte, utf8 英文 1byte/中文 3byte, unicode 2byte
的方式去一段段的读取解析,二进制文件的话并没有表征性的字符集,你可以按自己的需要解析,比如第1 byte
位代表的什么(unsigned short int / short int / ascii?),第2~3 byte
位代表的什么,第n~k byte
位代表的什么。
因为 ascii 的 char 和 int 的存储方式本质一致的(C语言里 char 类型本质就是 int),所以用多字节可能更好理解。
$chr) {
echo sprintf("%08d", decbin(ord($char))) . ' ';
}
// result
11100110 10001000 10010001
所以,如果我们使用 utf8
字符集处理 11100110 10001000 10010001
时,我们得到的是 “我”。
如果我们将其作为二进制文件处理的话得到的结果是int(15108241)
数值。
hex2bin / bin2hex
其实这俩货让我迷惑了很久,很长一段时间我都不明白他俩到底有什么用,输出结果不难理解,还被同事的代码带过节奏,因为我看到他经常在发送数据前通过 bin2hex
转化一下再发送,我惯性的认为难不成可以压缩数据量?
而这一次,我很刻意的将 hex2bin
写在了 bin2hex
的前面。
hex2bin 打包
hex2bin(hexString)
的作用是将字符串作为十六进制
的模式进行处理,如何处理,"68656c6c6f"
会被处理成"68" "65" "6c" "6c" "6f"
,然后转换成对应的二进制数据(注意:是二进制数据,不是二进制数据对应的二进制字符串,decbin 方法提供这一功能,将数据转换成二进制对应的字符串
),"68"(注意是字符串 2bytes)
转为二进制数值是 01101000(注意是数值 1byte)
,输出至终端其实就是h(1bytes)
的,依次处理后,我们成功的将10bytes
的字符串"68656c6c6f"
转换成了5bytes
的字符串"hello"
,只不过"hello"
并不是我们真正需要的数据,虽然你对它很亲切。
不要被"hello"
迷惑了,最初我看到它时很难把它和bin
联系到一起。"hello"
是你的编辑器对hex2bin
后的二进制流
进行处理时,把二进制流
当做文本
处理(它们是文本编辑器,自然会把数据作为文本处理),恰好能对应上可打印字符的ascii码
,就显示了"hello"
,所以说文本和二进制文件底层都是bit流
,看怎么处理了。
bin2hex 解包
bin2hex(binString)
则是将待处理的数据的二进制bit串
进行16进制
转换,并返回相应的16进制形式的字符串
,这里的bin
是说会将其作为二进制流,转换成对应的十六进制流,然后再以对应的字符串方式返回。比如"h"
的二进制bit串
是01101000
,对应的十六进制是 0x68
,相应的字符串形式是"68"
,依次继续解包处理 "e" "l" "l" "o"
后得到的字符串就是"68656c6c6f"
。
所以我们如果想传输字符集在'0-9a-f'
内的数据,可以使用 hex2bin
打包数据至二进制流节省空间,然后传输完成后再通过bin2hex
解包获得源数据。
hex2bin(hexString)/bin2hex(binString)
对应 pack("H*", hexString)/unpack("H*", binString)
pack/unpack
只要提供了socket
编程的语言,肯定也同时提供了 pack/unpack
,pack/unpack
的主要作用是方便我们将数据打包至二进制流,然后以二进制流的方式解包。
H 十六进制
将十六进制的字符串打包至二进制流字符串,这里有个很微妙的理解,比如字符串"68" 2bytes
,如果作为十六进制则其对应的二进制是00110110
,对应的ascii
文本字符是"h" 1byte
,传输后我们对h
进行解包转为十六进制得到"68"
,这种打包-传输-解包的方式节省了一半的传输量。
比如我们有一串字符串数据"68656c6c6f"
,如果直接传输的话需要strlen("68656c6c6f") = 10 byte
,你会发现这串数据完全符合十六进制模式,如果进行如下处理我们可以节省了一半的传输数据量。
以上功能可以用 hex2bin/bin2hex
实现
其实你会发现作为打包传输的数据是hello
,这里可能有些反客为主的惯性思维,因为大部分时间对我们有意义的数据是hello
而不是 68656c6c6f
,但在这里68656c6c6f
才是我们需要的数据,打包后得到的hello
反而是压缩传输的中间数据。
n 无符号16位整形大端序 / N 无符号32位整形大端序
n
是无符号短整型,固定2bytes
,可以将0 ~ 65536
内的数值用固定2bytes
表示,比如 "65536"
用字符串表示需要5bytes
,以 n
模式打包至二进制后只需要 2bytes
,节省了传输空间。
N
是无符号整形,固定4bytes
,可以将0~4294967296
内的数值用固定4bytes
表示。
当然,可能你的数值字符串是"0~99"
的时候节省空间的优点并不明显,但另一个优点一直存在,那就是长度固定
,在一些协议传输时dataLen . data
的包结构是很需要dataLen
是固定字节的数据。
比如
// package struct
00000000 00000001 00110110
|---dataLen 1---| |data h|
我们取数据包 package 的前2bytes
解包后得到的数值就是数据包消息体的长度,如上所示长度为 1,后面的data
即为1bytes 'h'
。
在tcp
流式数据时是很必须的,因为如果没有包长度声明,发送端连续发送多条消息,可能会导致粘包
,接收端没办法正确的拆分消息:msg1msg2msg3
可能会被拆分成 "msg" "1msg2ms" "g3"
。
而声明了包长度后,我们可以先读取固定长度的字节获取到消息体长度,再读取相应长度的消息体内容。
这样就可以灵活的解析流数据,在一些情况下还节省了空间。
其他模式大家可以参考: pack / unpack 参数详解