mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
1680 字
5 分钟
JavaScript二进制处理
2026-06-04

JavaScript 二进制数据处理详解#

在逆向工程和底层开发中,二进制数据处理是核心技能。本文详细介绍 JavaScript 中的 ArrayBuffer、TypedArray、DataView 以及相关位操作符。

目录


一、ArrayBuffer:二进制数据的容器#

1.1 什么是 ArrayBuffer#

ArrayBuffer 是一块原始的二进制数据缓冲区,它不可直接读写,必须通过视图(TypedArray 或 DataView)来操作。

// 创建一个 16 字节的缓冲区
const buffer = new ArrayBuffer(16);
console.log(buffer.byteLength); // 16
console.log(buffer instanceof ArrayBuffer); // true

关键特性

  • 固定长度,创建后无法改变大小
  • 存储的是原始二进制数据(字节序列)
  • 无法直接访问,需要视图

1.2 内存布局#

ArrayBuffer (16 bytes)
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │10 │11 │12 │13 │14 │15 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
↑ ↑
字节索引 0 字节索引 15

二、TypedArray:类型化数组视图#

2.1 TypedArray 类型一览#

类型字节数取值范围描述
Int8Array1-128 ~ 1278位有符号整数
Uint8Array10 ~ 2558位无符号整数
Uint8ClampedArray10 ~ 2558位无符号整数(溢出钳制)
Int16Array2-32768 ~ 3276716位有符号整数
Uint16Array20 ~ 6553516位无符号整数
Int32Array4-2147483648 ~ 214748364732位有符号整数
Uint32Array40 ~ 429496729532位无符号整数
Float32Array4IEEE 754 单精度浮点数32位浮点数
Float64Array8IEEE 754 双精度浮点数64位浮点数
BigInt64Array8-2^63 ~ 2^63-164位有符号 BigInt
BigUint64Array80 ~ 2^64-164位无符号 BigInt

2.2 Uint8Array:最常用的视图#

Uint8Array 是逆向中最常见的视图,每个元素代表一个字节(0-255)。

const buffer = new ArrayBuffer(4);
const uint8 = new Uint8Array(buffer);
// 写入数据
uint8[0] = 0x48; // 'H'
uint8[1] = 0x65; // 'e'
uint8[2] = 0x6C; // 'l'
uint8[3] = 0x6F; // 'o'
console.log(uint8); // Uint8Array(4) [72, 101, 108, 111]
// 溢出行为:取低8位
uint8[0] = 300; // 300 = 0x12C → 取低8位 0x2C = 44
console.log(uint8[0]); // 44
uint8[0] = -1; // -1 → 0xFF = 255
console.log(uint8[0]); // 255

2.3 Uint8ClampedArray:特殊的溢出处理#

Uint8Array 不同,Uint8ClampedArray 在溢出时会钳制到边界值:

const clamped = new Uint8ClampedArray(4);
const normal = new Uint8Array(4);
clamped[0] = 300; // 超出上限 → 钳制到 255
normal[0] = 300; // 取模 → 44
clamped[1] = -10; // 低于下限 → 钳制到 0
normal[1] = -10; // 取模 → 246
console.log(clamped[0], normal[0]); // 255, 44
console.log(clamped[1], normal[1]); // 0, 246

应用场景:Canvas 像素操作(RGBA 值必须在 0-255 范围内)

2.4 Int8Array:有符号字节#

const int8 = new Int8Array(4);
int8[0] = 127; // 最大值
int8[1] = -128; // 最小值
int8[2] = 255; // 255 作为有符号数 → -1
console.log(int8); // Int8Array(4) [127, -128, -1, 0]

有符号数的表示(补码):

正数:直接表示
127 = 0111 1111
负数:补码表示
-1 = 1111 1111 (255 的无符号表示)
-128 = 1000 0000 (128 的无符号表示)

2.5 16位视图:Int16Array / Uint16Array#

const buffer = new ArrayBuffer(4);
const uint16 = new Uint16Array(buffer);
const uint8 = new Uint8Array(buffer);
// 写入一个 16 位整数
uint16[0] = 0x1234;
// 查看字节表示(小端序)
console.log(uint8[0], uint8[1]); // 52, 18 → 0x34, 0x12

字节序问题

  • 小端序(Little-Endian):低位字节在前(x86/ARM 默认)
  • 大端序(Big-Endian):高位字节在前(网络协议)
数值 0x12345678 在内存中的存储:
小端序:78 56 34 12 (低地址存低位字节)
大端序:12 34 56 78 (低地址存高位字节)

2.6 32位视图:Int32Array / Uint32Array#

const buffer = new ArrayBuffer(8);
const uint32 = new Uint32Array(buffer);
const uint8 = new Uint8Array(buffer);
uint32[0] = 0xDEADBEEF;
// 小端序存储
console.log(uint8[0].toString(16)); // 'ef'
console.log(uint8[1].toString(16)); // 'be'
console.log(uint8[2].toString(16)); // 'ad'
console.log(uint8[3].toString(16)); // 'de'

2.7 浮点数视图:Float32Array / Float64Array#

const buffer = new ArrayBuffer(8);
const float32 = new Float32Array(buffer);
const float64 = new Float64Array(buffer);
const uint8 = new Uint8Array(buffer);
// 32位浮点数
float32[0] = 3.14159;
console.log(uint8.slice(0, 4)); // 查看字节表示
// 64位浮点数(JavaScript Number 的内部表示)
float64[0] = 3.14159;
console.log(uint8.slice(0, 8)); // 查看字节表示

IEEE 754 浮点数结构

Float32(32位单精度):
┌─┬────────┬───────────────────────┐
SExp(8) │ Mantissa(23) │
└─┴────────┴───────────────────────┘
Float64(64位双精度):
┌─┬──────────┬─────────────────────────────────┐
SExp(11) │ Mantissa(52) │
└─┴──────────┴─────────────────────────────────┘
S = 符号位 (0正1负)
Exp = 指数(偏移表示)
Mantissa = 尾数(隐含前导1)

2.8 BigInt 视图:BigInt64Array / BigUint64Array#

const buffer = new ArrayBuffer(16);
const bigInt64 = new BigInt64Array(buffer);
const bigUint64 = new BigUint64Array(buffer);
bigInt64[0] = 9223372036854775807n; // 最大正数 (2^63 - 1)
bigInt64[1] = -9223372036854775808n; // 最小负数 (-2^63)
bigUint64[0] = 18446744073709551615n; // 最大值 (2^64 - 1)
console.log(bigInt64[0]); // 9223372036854775807n
console.log(bigUint64[0]); // 18446744073709551615n

2.9 同一 ArrayBuffer 的多视图#

关键概念:多个视图可以共享同一个 ArrayBuffer,修改会相互影响。

const buffer = new ArrayBuffer(8);
const uint8 = new Uint8Array(buffer);
const uint32 = new Uint32Array(buffer);
const float64 = new Float64Array(buffer);
// 通过 uint32 写入
uint32[0] = 0x41424344;
// 通过 uint8 读取(查看字节)
console.log(String.fromCharCode(uint8[0], uint8[1], uint8[2], uint8[3]));
// 小端序:'DCBA' 或 'ABCD'(取决于字节序)
// 通过 float64 读取(无意义的浮点数)
console.log(float64[0]); // 一个很小的数

应用场景

  • 解析自定义二进制协议
  • 类型转换(如浮点数与整数的底层表示转换)
  • 加密算法实现

三、DataView:灵活的字节操作#

3.1 为什么需要 DataView#

TypedArray 有两个限制:

  1. 平台字节序固定(通常是小端序)
  2. 类型固定

DataView 可以:

  1. 自由指定字节序
  2. 在任意位置读写任意类型
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
// 在偏移 0 处写入 32 位整数(大端序)
view.setInt32(0, 0x12345678, false); // false = 大端序
// 在偏移 4 处写入 32 位整数(小端序)
view.setInt32(4, 0x12345678, true); // true = 小端序
// 在偏移 8 处写入 64 位浮点数
view.setFloat64(8, 3.14159, true);
// 读取
console.log(view.getInt32(0, false).toString(16)); // '12345678'
console.log(view.getInt32(4, true).toString(16)); // '12345678'

3.2 DataView 方法一览#

方法字节数描述
getInt8(offset)1读取有符号 8 位整数
getUint8(offset)1读取无符号 8 位整数
getInt16(offset, littleEndian)2读取有符号 16 位整数
getUint16(offset, littleEndian)2读取无符号 16 位整数
getInt32(offset, littleEndian)4读取有符号 32 位整数
getUint32(offset, littleEndian)4读取无符号 32 位整数
getFloat32(offset, littleEndian)4读取 32 位浮点数
getFloat64(offset, littleEndian)8读取 64 位浮点数
getBigInt64(offset, littleEndian)8读取有符号 64 位 BigInt
getBigUint64(offset, littleEndian)8读取无符号 64 位 BigInt
setInt8(offset, value)1写入有符号 8 位整数
setUint8(offset, value)1写入无符号 8 位整数
setInt16(offset, value, littleEndian)2写入有符号 16 位整数
setUint16(offset, value, littleEndian)2写入无符号 16 位整数
setInt32(offset, value, littleEndian)4写入有符号 32 位整数
setUint32(offset, value, littleEndian)4写入无符号 32 位整数
setFloat32(offset, value, littleEndian)4写入 32 位浮点数
setFloat64(offset, value, littleEndian)8写入 64 位浮点数
setBigInt64(offset, value, littleEndian)8写入有符号 64 位 BigInt
setBigUint64(offset, value, littleEndian)8写入无符号 64 位 BigInt

3.3 解析二进制协议示例#

// 模拟一个二进制协议头
// 格式:magic(4) + version(2) + flags(2) + length(4) + timestamp(8)
const buffer = new ArrayBuffer(20);
const view = new DataView(buffer);
// 写入协议头
view.setUint32(0, 0x89504E47, false); // magic (大端序)
view.setUint16(4, 1, false); // version
view.setUint16(6, 0x0001, false); // flags
view.setUint32(8, 1024, false); // length
view.setBigUint64(12, BigInt(Date.now()), false); // timestamp
// 解析协议头
function parseHeader(buffer) {
const view = new DataView(buffer);
return {
magic: view.getUint32(0, false).toString(16),
version: view.getUint16(4, false),
flags: view.getUint16(6, false),
length: view.getUint32(8, false),
timestamp: Number(view.getBigUint64(12, false))
};
}
console.log(parseHeader(buffer));

四、位操作符详解#

4.1 位操作符一览#

操作符名称描述
&按位与两位都为 1 时结果为 1
``按位或
^按位异或两位不同时结果为 1
~按位非翻转所有位
<<左移左移 n 位,低位补 0
>>算术右移右移 n 位,高位补符号位
>>>无符号右移右移 n 位,高位补 0

4.2 按位与(&)#

// 提取特定位
const value = 0b11010110;
// 提取低 4 位
console.log((value & 0b1111).toString(2)); // '110'
// 提取第 3 位
console.log((value & 0b00100000) !== 0); // true
// 判断奇偶
console.log((5 & 1) === 1); // true (奇数)
console.log((4 & 1) === 0); // true (偶数)
// 掩码操作
const permissions = 0b1011; // 读、写、执行
const READ = 0b0001;
const WRITE = 0b0010;
const EXECUTE = 0b1000;
console.log(permissions & READ); // 1 (有读权限)
console.log(permissions & WRITE); // 2 (有写权限)
console.log(permissions & EXECUTE); // 8 (有执行权限)

4.3 按位或(|)#

// 设置特定位
let flags = 0b0000;
flags |= 0b0001; // 设置第 0 位
flags |= 0b1000; // 设置第 3 位
console.log(flags.toString(2)); // '1001'
// 合并值
const red = 0xFF0000;
const green = 0x00FF00;
const blue = 0x0000FF;
const white = red | green | blue; // 0xFFFFFF

4.4 按位异或(^)#

// 翻转特定位
let value = 0b1010;
value ^= 0b1111; // 翻转所有位
console.log(value.toString(2)); // '101'
// 简单加密
const key = 0x55;
const data = 0x41; // 'A'
const encrypted = data ^ key; // 0x14
const decrypted = encrypted ^ key; // 0x41
// 交换两个数(不使用临时变量)
let a = 5, b = 3;
a ^= b;
b ^= a;
a ^= b;
console.log(a, b); // 3, 5

4.5 按位非(~)#

// 按位非的结果 = -(x + 1)
console.log(~0); // -1
console.log(~(-1)); // 0
console.log(~5); // -6
// 快速判断数组中是否存在元素
const arr = [1, 2, 3, 4, 5];
const index = arr.indexOf(3);
if (~index) { // index >= 0 时 ~index 为真
console.log('找到了');
}
// 提取最低位的 1
const x = 0b101000;
const lowestBit = x & -x; // 或 x & (~x + 1)
console.log(lowestBit.toString(2)); // '1000'

4.6 左移(<<)#

// 乘以 2 的 n 次方
console.log(5 << 1); // 10 (5 * 2)
console.log(5 << 2); // 20 (5 * 4)
console.log(5 << 3); // 40 (5 * 8)
// 组合字节
const high = 0x12;
const low = 0x34;
const combined = (high << 8) | low; // 0x1234
// 构造掩码
const mask = 1 << 5; // 0b100000

4.7 算术右移(>>)#

// 除以 2 的 n 次方(保留符号)
console.log(8 >> 1); // 4
console.log(8 >> 2); // 2
console.log(-8 >> 1); // -4 (保留符号位)
// 提取高位字节
const value = 0x1234;
const highByte = value >> 8; // 0x12

4.8 无符号右移(>>>)#

// 无符号右移(高位补 0)
console.log(-8 >>> 1); // 2147483644 (不是 -4!)
// 将 32 位有符号数转为无符号数
const signed = -1;
const unsigned = signed >>> 0; // 4294967295
// 限制为 32 位无符号整数
function toUint32(x) {
return x >>> 0;
}
console.log(toUint32(-1)); // 4294967295
console.log(toUint32(0xFFFFFFFF)); // 4294967295
console.log(toUint32(0x1FFFFFFFF)); // 4294967295 (截断高32位)

五、实用技巧与模式#

5.1 字符串与 ArrayBuffer 互转#

// 字符串 → Uint8Array(UTF-8 编码)
function stringToUint8Array(str) {
return new TextEncoder().encode(str);
}
// Uint8Array → 字符串(UTF-8 解码)
function uint8ArrayToString(uint8) {
return new TextDecoder().decode(uint8);
}
// 示例
const str = "Hello, 世界!";
const bytes = stringToUint8Array(str);
console.log(bytes);
// Uint8Array(13) [72, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140]
const decoded = uint8ArrayToString(bytes);
console.log(decoded); // "Hello, 世界!"

5.2 十六进制字符串与 ArrayBuffer 互转#

// ArrayBuffer → 十六进制字符串
function bufferToHex(buffer) {
return Array.from(new Uint8Array(buffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// 十六进制字符串 → ArrayBuffer
function hexToBuffer(hex) {
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
}
return bytes.buffer;
}
// 示例
const buffer = new ArrayBuffer(4);
new Uint8Array(buffer).set([0xDE, 0xAD, 0xBE, 0xEF]);
console.log(bufferToHex(buffer)); // 'deadbeef'
const restored = hexToBuffer('deadbeef');
console.log(new Uint8Array(restored)); // Uint8Array(4) [222, 173, 190, 239]

5.3 Base64 与 ArrayBuffer 互转#

// ArrayBuffer → Base64
function bufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
// Base64 → ArrayBuffer
function base64ToBuffer(base64) {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
}
// 示例
const buffer = new ArrayBuffer(4);
new Uint8Array(buffer).set([72, 101, 108, 108]);
console.log(bufferToBase64(buffer)); // 'SGVsbA=='

5.4 类型转换技巧#

// 浮点数与整数的底层表示转换
function floatToBytes(num) {
const buffer = new ArrayBuffer(8);
new Float64Array(buffer)[0] = num;
return new Uint8Array(buffer);
}
function bytesToFloat(bytes) {
const buffer = new ArrayBuffer(8);
new Uint8Array(buffer).set(bytes);
return new Float64Array(buffer)[0];
}
// 示例
const bytes = floatToBytes(3.14159);
console.log(bytes);
// Uint8Array(8) [82, 232, 25, 26, 133, 235, 9, 64]
console.log(bytesToFloat(bytes)); // 3.14159
// 32 位整数与浮点数互转
function intBitsToFloat(bits) {
const buffer = new ArrayBuffer(4);
new Uint32Array(buffer)[0] = bits;
return new Float32Array(buffer)[0];
}
function floatToIntBits(num) {
const buffer = new ArrayBuffer(4);
new Float32Array(buffer)[0] = num;
return new Uint32Array(buffer)[0];
}

5.5 字节序转换#

// 翻转字节序(大端 ↔ 小端)
function swapEndian16(value) {
return ((value & 0xFF) << 8) | ((value >> 8) & 0xFF);
}
function swapEndian32(value) {
return ((value & 0xFF) << 24) |
((value & 0xFF00) << 8) |
((value >> 8) & 0xFF00) |
((value >> 24) & 0xFF);
}
// 示例
console.log(swapEndian16(0x1234).toString(16)); // '3412'
console.log(swapEndian32(0x12345678).toString(16)); // '78563412'
// 使用 DataView 处理字节序
function readLittleEndianUint32(buffer, offset) {
const view = new DataView(buffer);
return view.getUint32(offset, true); // true = 小端序
}
function readBigEndianUint32(buffer, offset) {
const view = new DataView(buffer);
return view.getUint32(offset, false); // false = 大端序
}

5.6 位域操作#

// 提取位域
function extractBits(value, start, length) {
const mask = (1 << length) - 1;
return (value >>> start) & mask;
}
// 设置位域
function setBits(value, start, length, newValue) {
const mask = (1 << length) - 1;
return (value & ~(mask << start)) | ((newValue & mask) << start);
}
// 示例
const value = 0b11010110;
console.log(extractBits(value, 2, 3).toString(2)); // '101'
const modified = setBits(value, 2, 3, 0b111);
console.log(modified.toString(2)); // '11011110'

六、逆向中的常见应用#

6.1 解析加密数据#

// 常见的加密数据结构
function parseEncryptedData(buffer) {
const view = new DataView(buffer);
const result = {};
// 读取魔数
result.magic = view.getUint32(0, false);
// 读取版本
result.version = view.getUint16(4, false);
// 读取标志位
result.flags = view.getUint16(6, false);
// 读取数据长度
result.length = view.getUint32(8, false);
// 读取时间戳
result.timestamp = Number(view.getBigUint64(12, false));
// 读取数据
result.data = new Uint8Array(buffer, 20, result.length);
return result;
}

6.2 模拟 Hash 算法#

// 简单的循环移位
function rotateLeft(value, shift, bits = 32) {
shift = shift % bits;
return ((value << shift) | (value >>> (bits - shift))) >>> 0;
}
function rotateRight(value, shift, bits = 32) {
shift = shift % bits;
return ((value >>> shift) | (value << (bits - shift))) >>> 0;
}
// 示例
console.log(rotateLeft(0x12345678, 4).toString(16)); // '23456781'
console.log(rotateRight(0x12345678, 4).toString(16)); // '81234567'

6.3 分析混淆代码#

// 常见的混淆模式:字符串数组索引
const stringArray = ['log', 'warn', 'error', 'info'];
const indexMap = { 0x0: 0, 0x1: 1, 0x2: 2, 0x3: 3 };
function getString(encodedIndex) {
return stringArray[indexMap[encodedIndex]];
}
// 常见的混淆模式:位运算加密
function decryptChar(encrypted, key) {
return String.fromCharCode(encrypted ^ key);
}
// 分析时识别这些模式
// _0x1234 ^ _0x5678 → 异或解密
// _0x1234 << 2 → 左移乘法
// _0x1234 >> 1 → 右移除法
// _0x1234 & 0xFF → 取低字节

七、性能优化建议#

7.1 避免频繁创建视图#

// 不好的做法
for (let i = 0; i < 1000; i++) {
const view = new DataView(buffer);
view.setInt32(0, i);
}
// 好的做法
const view = new DataView(buffer);
for (let i = 0; i < 1000; i++) {
view.setInt32(0, i);
}

7.2 使用 slice 而非 copy#

// 不好的做法:手动复制
function copyBytes(buffer, start, length) {
const result = new Uint8Array(length);
const source = new Uint8Array(buffer);
for (let i = 0; i < length; i++) {
result[i] = source[start + i];
}
return result;
}
// 好的做法:使用 slice
function sliceBytes(buffer, start, length) {
return new Uint8Array(buffer, start, length).slice();
}

7.3 批量操作#

// 不好的做法:逐字节操作
for (let i = 0; i < data.length; i++) {
view.setUint8(i, data[i]);
}
// 好的做法:批量设置
new Uint8Array(buffer).set(data);

八、常见陷阱与注意事项#

8.1 TypedArray 长度计算#

const buffer = new ArrayBuffer(16);
// TypedArray 长度 = buffer 字节数 / 每个元素字节数
console.log(new Uint8Array(buffer).length); // 16
console.log(new Uint16Array(buffer).length); // 8
console.log(new Uint32Array(buffer).length); // 4
console.log(new Float64Array(buffer).length);// 2
// 带偏移和长度的视图
const view = new Uint8Array(buffer, 4, 8); // 从偏移 4 开始,长度 8
console.log(view.length); // 8

8.2 溢出行为#

const uint8 = new Uint8Array(1);
const int8 = new Int8Array(1);
// Uint8Array: 溢出时取模
uint8[0] = 256; // 256 % 256 = 0
console.log(uint8[0]); // 0
uint8[0] = -1; // -1 & 0xFF = 255
console.log(uint8[0]); // 255
// Int8Array: 溢出时取模并解释为有符号数
int8[0] = 128; // 128 % 256 = 128 → 解释为 -128
console.log(int8[0]); // -128

8.3 字节序陷阱#

// 平台字节序检测
const isLittleEndian = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
console.log(isLittleEndian ? '小端序' : '大端序');
// 网络协议通常使用大端序
// x86/ARM 通常使用小端序
// 使用 DataView 明确指定字节序可以避免问题

8.4 内存对齐#

// 某些平台要求内存对齐
const buffer = new ArrayBuffer(16);
const uint32 = new Uint32Array(buffer, 1); // 偏移 1 字节(未对齐)
// 在某些平台上可能导致性能下降或错误
// 建议:使用 4 或 8 字节对齐的偏移
const aligned = new Uint32Array(buffer, 4); // 偏移 4 字节(对齐)

九、总结#

概念用途
ArrayBuffer原始二进制数据容器
TypedArray类型化数组视图,快速读写特定类型
DataView灵活的字节操作,支持字节序控制
Uint8Array字节级操作,逆向最常用
位操作符底层数据处理、加密算法

选择建议

  • 处理字节数据 → Uint8Array
  • 需要控制字节序 → DataView
  • 处理二进制协议 → DataView + Uint8Array
  • 加密算法实现 → 位操作符 + Uint32Array

参考资料#

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

JavaScript二进制处理
https://fatdog.20060113.xyz/posts/js-buffer/
作者
神秘大胖狗
发布于
2026-06-04
许可协议
MIT

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00