开始前,会这些就够了
- 知道一个字节有 8 位
- 能读懂简单
JavaScript
遇到不熟悉的词,先看这 6 个解释
- 位
- 位是表示二选一状态的基本单位,通常写成零或一。许多位按约定组合起来,才能表达更大的数字、文字或图片,单独一位并不会告诉你它属于哪种数据。
- 字节
- 在这里介绍的现代文件和网络格式中,一个字节由八个位组成,常用来计量数据大小。它有二百五十六种位组合,但这些组合代表什么仍由格式规定。
- 字符编码
- 字符编码是把文字与可存储的数据对应起来的规则。写入时按规则产生数据,读取时按相应规则还原文字;双方使用不同规则,就可能看到乱码或替代符号。
- UTF-8
- 它是把统一字符编号写成字节序列的一种编码方式,长度并不固定。一个可编码的字符编号会使用一到四个字节,所以文字数量与文件字节数通常不能直接画等号。
- 字节序
- 字节序规定一个数拆成多个字节后,按什么顺序保存或传输。大端把较高位的字节放在前面,小端把较低位的放在前面;读取时必须采用相同约定才能还原数值。
- 内存
- 这里的内存主要指程序运行时使用的随机存取内存,用来放当前要处理的代码和数据。它适合频繁读写,普通电脑中的这类内存通常不能在断电后保留内容。
假设你收到四个字节:01、02、03、04。你以为它表示一个很小的编号,程序却读出了另一个大数。数据可能没有丢,也没有被加密;双方只是把哪一端当作数字的开头这件事理解得不一样。
这种分歧叫字节序。它处理的是一个多字节数怎样排在存储空间里。先看一个明确的四字节整数,比背“大端”和“小端”两个词更容易理解。
四个字节,为什么有两种排列?
数值:305419896(十进制)
12
地址 +034
地址 +156
地址 +278
地址 +3
78
地址 +056
地址 +134
地址 +212
地址 +3
这是无符号 32 位整数的字节顺序示意。从左到右表示地址由低到高,不是把每个字节里的二进制位反过来;两种顺序表示的数值相同。
先把数字拆成四份
十六进制的 0x01020304 可以拆成 01、02、03、04 四个字节。每向左移动一个字节,所代表的位权就乘以 256。因此 01 是最高有效字节,04 是最低有效字节;“有效”在这里说的是对数值大小的贡献,不是说某个字节更有用。
现在给它准备四个连续位置,从低地址往高地址看。大端把最高有效字节放在前面,得到 01 02 03 04;小端把最低有效字节放在前面,得到 04 03 02 01。整数本身没有变,改变的是它在这四个位置里的摆法。
只改读取方式,结果就变了
下面先明确按大端写入,再把同一块空间分别按大端和小端读取。DataView 允许直接指定字节序;false 表示大端,true 表示小端。这样不用猜自己的电脑采用什么顺序,也不会让实验结果依赖处理器型号。
第二种读法把原本较小位权的字节放到了较大位权的位置,所以得到不同的整数。注意输出使用十六进制,开头的零不会自动保留。数字 1020304 与写入时的 01020304 是同一个数,少的是显示填充,不是数据。
const bytes = new ArrayBuffer(4);
const view = new DataView(bytes);
view.setUint32(0, 0x01020304, false);
console.log([...new Uint8Array(bytes)].map(x => x.toString(16).padStart(2, "0")).join(" "));
console.log(view.getUint32(0, false).toString(16));
console.log(view.getUint32(0, true).toString(16));怎样运行:在现代浏览器控制台或 Node.js 中运行;只创建四字节内存,不读写文件。
运行后对照结果
01 02 03 04
1020304
4030201先问文件规定,别先问电脑型号
假设一个文件格式规定,前四字节是按小端保存的图片宽度。即使读取它的机器本身偏爱大端,也必须遵守文件格式。网络消息同样如此:两台机器可以习惯不同,只要写入方和读取方按同一份约定转换,就能正确交换数据。
所以排查二进制文件时,可以先记下原始字节、字段宽度、是否带符号和规定字节序,再计算解释结果。只看最终显示的十进制数字,很容易把顺序错误误判成传输损坏。一次交换两个字节或读取错位置,又是另外的错误。例如长度字段写错顺序时,程序可能以为后面还有大量数据,却实际上已经到达文件末尾。先核对这个字段,往往比继续猜测后面的内容更有帮助。
小端不等于把文字全部倒过来
字节序不是“整个文件从右向左读”。若文件依次保存宽度和高度,应分别按两个字段的规则解释,不能把两个字段一起翻转。一个字节内部的位怎样编号,也与多个字节怎样排列是不同问题,不要把两种顺序混为一谈。
文本还需要另看字符编码。UTF-8 按自身规则安排字节,不能因为电脑是小端就反转一句话。接触 UTF-16、图片头或网络整数时,字节序才会以对应格式规定的方式出现。遇到陌生数据,先找格式说明,再选择读取函数。
记住这三点
- 大小端决定多字节数的字节排列方式,不改变那个数原本的数学值。
- 读取数据应服从文件或协议规定,并明确字段宽度和是否带符号。
DataView可以显式选择顺序,适合观察同一串字节的不同解释。
别把例子用过头
- 不能把整个文件倒序当成通用的大小端转换。
- 这个实验没有测量处理器的原生字节序;它主动指定了读写规则。
自己试一小步
怎样构造一个“顺序读错了,但结果碰巧没变”的四字节整数?
- 先在纸上写出四个十六进制字节,预测反转后的排列。
- 替换示例中的整数,并保留两种读取方式。
- 再只改其中一个字节,观察原先的巧合是否仍成立。
做到这些,就算完成
- 能把预测字节与实际输出对应起来。
- 能解释一次结果相同为什么不足以证明读取规则正确。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- 字节序 (新标签页)↗
大小端的定义和多字节数排列示例。
- DataView.prototype.setUint32() (新标签页)↗
写入 32 位无符号整数及
littleEndian参数的含义。
找一份资料继续练
先选一项,按下面的范围继续学;不必从头读完整门课程。
MDN 中文:同四个字节,按什么顺序读成数字? (新标签页)↗
保持四个字节不变,只换 littleEndian 参数,写出两种十六进制结果。
查看学习准备与访问说明 →