在 JavaScript 里,Uint8Array
、Int32Array
、DataView
与 ArrayBuffer
都是用于处理二进制数的。
1. ArrayBuffer
ArrayBuffer
是一种基础对象,用于在内存里存储固定长度的二进制数据。可以把它想象成一块连续的、未经过解释的内存区域,其长度在创建时就已确定,之后无法改变。
// 创建一个长度为 16 字节的 ArrayBuffer
const buffer = new ArrayBuffer(16);
2. Uint8Array
和 Int32Array
Uint8Array
和 Int32Array
属于类型化数组(TypedArray)。类型化数组为 ArrayBuffer
提供了一种视图,允许你以特定的数据类型来读写 ArrayBuffer
里的数据。
Uint8Array
:以无符号 8 位整数(范围从 0 到 255)的形式来读写数据。每个元素占 1 个字节。
const buffer = new ArrayBuffer(16);
const uint8Array = new Uint8Array(buffer);
uint8Array[0] = 255; // 写入一个无符号 8 位整数
console.log(uint8Array[0]); // 输出 255
Int32Array
:以有符号 32 位整数(范围从 -2147483648 到 2147483647)的形式来读写数据。每个元素占 4 个字节。
const buffer = new ArrayBuffer(16);
const int32Array = new Int32Array(buffer);
int32Array[0] = 123456789; // 写入一个有符号 32 位整数
console.log(int32Array[0]); // 输出 123456789
3. 三者之间的关系
ArrayBuffer
是数据的存储容器,它代表的是一块原始的二进制内存。Uint8Array
和Int32Array
是对ArrayBuffer
的视图,它们提供了不同的数据访问方式。多个类型化数组可以同时引用同一个ArrayBuffer
,并且能以不同的数据类型来访问其中的数据。
const buffer = new ArrayBuffer(16);
const uint8Array = new Uint8Array(buffer);
const int32Array = new Int32Array(buffer);
// 修改 uint8Array 会影响 int32Array
uint8Array[0] = 1;
uint8Array[1] = 0;
uint8Array[2] = 0;
uint8Array[3] = 0;
console.log(int32Array[0]); // 输出 1
综上所述,ArrayBuffer
是底层的数据存储,而 Uint8Array
和 Int32Array
是对这块数据进行操作的接口。通过不同的类型化数组,可以按照不同的数据类型来访问和处理 ArrayBuffer
中的数据。
DataView
DataView
是 JavaScript 中用于处理二进制数据的一个强大工具,它和 Uint8Array
、Int32Array
一样,都是基于 ArrayBuffer
来操作二进制数据,但 DataView
提供了更灵活和细致的控制。下面为你详细介绍它的相关内容。
DataView 基本概念
DataView
提供了一个抽象层,允许你以任意字节偏移量和不同的数据类型(如整数、浮点数等)来读写 ArrayBuffer
中的数据。与类型化数组不同,DataView
不会受限于特定的数据类型和字节顺序(大端或小端),你可以在同一个 ArrayBuffer
上根据需要使用不同的数据类型和字节顺序进行读写操作。
创建 DataView
你可以通过传入一个 ArrayBuffer
实例来创建 DataView
对象,也可以指定起始字节偏移量和视图的长度。
// 创建一个长度为 16 字节的 ArrayBuffer
const buffer = new ArrayBuffer(16);
// 创建一个 DataView,从 ArrayBuffer 的起始位置开始
const view = new DataView(buffer);
// 创建一个 DataView,从第 4 个字节开始,长度为 8 字节
const partialView = new DataView(buffer, 4, 8);
读写数据
DataView
提供了一系列方法用于读写不同类型的数据,以下是一些常用方法的示例:
写入数据
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
// 写入一个 32 位有符号整数
view.setInt32(0, 123456789, false); // 第三个参数表示是否使用小端字节序,false 表示大端字节序
// 写入一个 64 位浮点数
view.setFloat64(4, 3.141592653589793, true); // 使用小端字节序
读取数据
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
// 假设之前已经写入了数据
const intValue = view.getInt32(0, false); // 读取 32 位有符号整数
const floatValue = view.getFloat64(4, true); // 读取 64 位浮点数
console.log(intValue);
console.log(floatValue);
字节顺序
字节顺序指的是多字节数据在内存中存储的顺序,分为大端字节序(Big Endian)和小端字节序(Little Endian)。大端字节序是指最高有效字节存储在最低地址,而小端字节序则是最低有效字节存储在最低地址。在 DataView
的读写方法中,你可以通过第三个参数来指定使用哪种字节顺序。
大小端字节序的概念
在计算机系统中,当存储多字节数据(如整数、浮点数等)时,由于内存地址是按字节编址的,就涉及到这些字节在内存中的存储顺序问题,这便产生了大小端字节序的概念。
- 大端字节序(Big - Endian) :也被称作网络字节序。在大端字节序里,数据的最高有效字节(MSB,Most Significant Byte)存于最低内存地址处,而最低有效字节(LSB,Least Significant Byte)存于最高内存地址处。这与人们书写数字的习惯相符。例如,对于一个 32 位整数
0x12345678
,在大端字节序下,内存中的存储顺序为0x12
、0x34
、0x56
、0x78
。 - 小端字节序(Little - Endian) :在小端字节序中,数据的最低有效字节存于最低内存地址处,最高有效字节存于最高内存地址处。对于 32 位整数
0x12345678
,在小端字节序下,内存中的存储顺序为0x78
、0x56
、0x34
、0x12
。
为什么要区分大小端字节,他们是为了解决什么问题,在哪些领域和场景有应用?
硬件设计因素
不同的 CPU 架构在设计时,出于性能、成本、实现复杂度等方面的考虑,会采用不同的字节序。例如,x86 架构的 CPU 采用小端字节序,而 PowerPC 等一些 CPU 采用大端字节序。这是因为不同的字节序在硬件实现上可能有不同的优势,比如小端字节序在进行数据的低位操作时可能更方便,而大端字节序在某些数据处理算法中可能更符合逻辑。
数据传输和兼容性问题
在计算机网络和数据存储中,不同的设备可能采用不同的字节序。为了确保数据在不同设备之间能够正确传输和解读,就需要明确规定字节序。例如,在网络通信中,如果不统一字节序,接收方可能无法正确解析发送方发送的数据。
应用场景
计算机网络
在网络通信中,为了保证不同计算机之间的数据能够正确传输和解析,通常采用大端字节序作为网络字节序。例如,在 TCP/IP 协议中,IP 地址、端口号等多字节数据都是以大端字节序进行传输的。当一台小端字节序的计算机要发送数据到网络上时,需要将数据转换为大端字节序;而接收数据时,需要将大端字节序的数据转换为小端字节序进行处理。
嵌入式系统
在嵌入式系统中,不同的处理器可能采用不同的字节序。例如,一些微控制器采用小端字节序,而一些高端嵌入式处理器可能采用大端字节序。在设计嵌入式系统时,需要考虑字节序的问题,确保数据在不同模块之间能够正确传输和处理。
数据存储
在文件存储和数据库系统中,也需要考虑字节序的问题。例如,一些二进制文件格式(如 BMP 图像文件)会明确规定数据的字节序,以确保不同计算机上的软件能够正确读取和处理这些文件。
跨平台开发
在跨平台开发中,由于不同平台可能采用不同的字节序,开发人员需要特别注意数据的存储和传输。例如,在 Java 中,ByteBuffer
类提供了 order()
方法来设置字节序,以确保在不同平台上的数据处理一致性。