在JavaScript中构建二进制数据结构及高效数据序列化方案问询
在JavaScript中实现高效自定义序列化的方案
我完全懂你怀念Flash那套自定义序列化的便捷性——JSON确实太啰嗦了,尤其是当你有大量重复结构的数据时,键名的重复传输会浪费不少带宽,而且所有类型都转成字符串也会额外增加体积。下面给你几个适配Node.js和浏览器的方案,从原生手动实现到成熟库都有:
一、原生API手动实现(类似Flash ByteArray)
JavaScript的ArrayBuffer + DataView组合完全可以模拟Flash的序列化逻辑,你可以精确控制每个字段的字节长度,完全自定义序列化规则,没有冗余。
核心思路:
- 用
DataView操作ArrayBuffer,它支持写入/读取各种数值类型(比如16位短整数、32位无符号整数等),和Flash的writeShort/writeUnsignedInt一一对应。 - 序列化完成后,把
ArrayBuffer转成Base64字符串用于传输;反序列化时则反向操作。
示例代码:
先封装一个类似Flash ByteArray的工具类:
class ByteArray { constructor(buffer = new ArrayBuffer(1024)) { this.view = new DataView(buffer); this.position = 0; } // 写入16位有符号整数 writeShort(value) { this.view.setInt16(this.position, value, true); // true表示小端字节序,可根据需求调整 this.position += 2; } // 写入32位无符号整数 writeUnsignedInt(value) { this.view.setUint32(this.position, value, true); this.position += 4; } // 写入任意JSON可序列化数据(如果需要处理复杂对象) writeObject(obj) { const jsonStr = JSON.stringify(obj); const encoder = new TextEncoder(); const strBytes = encoder.encode(jsonStr); // 先写入字符串长度,方便反序列化时读取 this.writeUnsignedInt(strBytes.length); new Uint8Array(this.view.buffer, this.position, strBytes.length).set(strBytes); this.position += strBytes.length; } // 读取16位有符号整数 readShort() { const value = this.view.getInt16(this.position, true); this.position += 2; return value; } // 读取32位无符号整数 readUnsignedInt() { const value = this.view.getUint32(this.position, true); this.position += 4; return value; } // 读取对象 readObject() { const length = this.readUnsignedInt(); const strBytes = new Uint8Array(this.view.buffer, this.position, length); this.position += length; const decoder = new TextDecoder(); const jsonStr = decoder.decode(strBytes); return JSON.parse(jsonStr); } // 转成Base64字符串(兼容Node和浏览器) toBase64() { if (typeof Buffer !== 'undefined') { // Node.js环境 return Buffer.from(this.view.buffer.slice(0, this.position)).toString('base64'); } else { // 浏览器环境 const uint8Array = new Uint8Array(this.view.buffer.slice(0, this.position)); let binary = ''; for (let i = 0; i < uint8Array.length; i++) { binary += String.fromCharCode(uint8Array[i]); } return btoa(binary); } } // 从Base64字符串加载数据 static fromBase64(base64Str) { let buffer; if (typeof Buffer !== 'undefined') { // Node.js环境 buffer = Buffer.from(base64Str, 'base64'); } else { // 浏览器环境 const binary = atob(base64Str); buffer = new ArrayBuffer(binary.length); const uint8Array = new Uint8Array(buffer); for (let i = 0; i < binary.length; i++) { uint8Array[i] = binary.charCodeAt(i); } } return new ByteArray(buffer); } }
然后用这个类实现你的数据序列化/反序列化:
// 序列化示例 function serializeUserData(session, itemID, arbitraryData, score) { const byteArray = new ByteArray(); byteArray.writeShort(session); byteArray.writeUnsignedInt(itemID); byteArray.writeObject(arbitraryData); byteArray.writeShort(score); return byteArray.toBase64(); } // 反序列化示例 function deserializeUserData(base64Str) { const byteArray = ByteArray.fromBase64(base64Str); return { session: byteArray.readShort(), itemID: byteArray.readUnsignedInt(), arbitraryData: byteArray.readObject(), score: byteArray.readShort() }; } // 使用示例 const serialized = serializeUserData(123, 456789, {name: "test", enabled: true}, 99); console.log('序列化后Base64:', serialized); const deserialized = deserializeUserData(serialized); console.log('反序列化结果:', deserialized);
二、成熟库推荐(减少手动编码工作量)
如果你不想自己写底层逻辑,下面这些库都支持Node.js和浏览器,且序列化效率远高于JSON:
1. MessagePack(msgpack-lite)
MessagePack是一种二进制序列化格式,比JSON更紧凑,同时支持自定义类型的序列化规则,用法和JSON类似但体积更小。
示例用法:
const msgpack = require('msgpack-lite'); // Node.js环境;浏览器可以通过<script>引入或打包工具 // 序列化 const data = {session: 123, itemID: 456789, arbitraryData: {name: "test"}, score: 99}; const buffer = msgpack.encode(data); const base64 = Buffer.from(buffer).toString('base64'); // Node.js;浏览器用Uint8Array转Base64 // 反序列化 const decodedBuffer = Buffer.from(base64, 'base64'); // Node.js;浏览器用atob转成ArrayBuffer const decodedData = msgpack.decode(decodedBuffer);
2. Protocol Buffers(protobufjs)
Protocol Buffers是Google开发的高效序列化协议,需要先定义数据结构的Schema,然后自动生成序列化/反序列化代码,适合结构化数据,体积极小,性能极佳。
示例步骤:
- 定义Schema(比如
userdata.proto):
syntax = "proto3"; message UserData { int32 session = 1; uint32 itemID = 2; map<string, string> arbitraryData = 3; // 或者自定义嵌套类型 int32 score = 4; }
- 用protobufjs生成JS代码,或者直接动态加载Schema:
const protobuf = require("protobufjs"); // Node.js;浏览器支持 // 加载Schema并生成类型 protobuf.load("userdata.proto", (err, root) => { if (err) throw err; const UserData = root.lookupType("UserData"); // 准备数据 const data = {session: 123, itemID: 456789, arbitraryData: {name: "test"}, score: 99}; // 验证数据(可选) const errMsg = UserData.verify(data); if (errMsg) throw Error(errMsg); // 序列化 const message = UserData.create(data); const buffer = UserData.encode(message).finish(); const base64 = Buffer.from(buffer).toString('base64'); // 反序列化 const decodedMessage = UserData.decode(Buffer.from(base64, 'base64')); const decodedData = UserData.toObject(decodedMessage); console.log(decodedData); });
3. FlatBuffers
FlatBuffers也是Google的产品,特点是序列化后的数据不需要解析就能直接访问,性能极高,适合对延迟和带宽要求极高的场景,用法和Protocol Buffers类似,需要定义Schema。
三、方案选择建议
- 如果需要完全自定义序列化逻辑(和Flash那套完全对齐),选原生ArrayBuffer+DataView的方案,灵活性最高。
- 如果想兼顾开发效率和压缩率,选MessagePack,学习成本低,无需定义Schema。
- 如果是大型项目、结构化数据,追求极致性能和压缩率,选Protocol Buffers或FlatBuffers。
内容的提问来源于stack exchange,提问作者Jim Sreven
相关产品推荐
相关产品推荐

