JavaScript 中用于处理二进制数据的API主要有7个:[ArrayBuffer、TypedArray、DataView、Blob、FileReader、URL.createObjectURL、websocket的send方法]下面进行总结
更加标准的文档,请参考:
ArrayBuffer - ECMAScript 6入门
TypedArray - JavaScript | MDN
袁进的博客
一、ArrayBuffer
定义
ArrayBuffer 是一个用于表示通用的、固定长度的二进制数据缓冲区的对象。它在内存中分配了一块连续的字节区域,但不能直接操作这些字节,需要通过 TypedArray 或 DataView 来访问和修改其中的数据。
修改方法
// 创建一个 ArrayBuffer,分配 16 字节的内存
const buffer = new ArrayBuffer(16);
// 使用 TypedArray‘s【Uint8Array】 访问 ArrayBuffer
const uint8Array = new Uint8Array(buffer);
// 向 Uint8Array 中写入数据
uint8Array[0] = 255;
uint8Array[1] = 128;
// 使用 DataView 访问 ArrayBuffer
const dataView = new DataView(buffer);
// 从 DataView 中读取数据
const firstByte = dataView.getUint8(0);
const secondByte = dataView.getUint8(1);
console.log('Using TypedArray:', uint8Array[0], uint8Array[1]);
console.log('Using DataView:', firstByte, secondByte);
与字符串之间的转换
ArrayBuffer本身不支持直接与字符串交互。需要通过 TextEncoder 或 TextDecoder 来处理字符串和二进制数据的转换。
const serializedChineseText = '这里是一些中文数据序列化后的结果';
const encoder = new TextEncoder();
const uint8ArrayData = encoder.encode(serializedChineseText); // 处理 uint8ArrayData
const decoder = new TextDecoder();
const deserializedText = decoder.decode(uint8ArrayData); console.log(deserializedText);
二、TypedArray
定义
TypedArray 是一组构造函数 [Init8Array、Uint8Array、Init16Array、Init32Array、Uint32Array、Float32Array、Float64Arra] 的统称。
Init8Array是8位符号整数,多余的0划给负数,取值范围[-128 ~ 127]
Uint8Array无符号整数取值[0 ~ 255]
当在TypedArray中写入的数据的大小超出其范围时,数据会出现紊乱
特性
- TypedArray 实例是存储在ArrayBuffer中的,如果在创建时没有显式传入
ArrayBuffer,它会自动创建一个新的 ArrayBuffer 来存储数据。
// 创建一个长度为 5 的 Uint8Array,会自动创建一个 ArrayBuffer
const typedArray = new Uint8Array(5);
// 可以通过 buffer 属性访问底层的 ArrayBuffer
const underlyingBuffer = typedArray.buffer;
console.log(underlyingBuffer instanceof ArrayBuffer);
// 输出: true
- 如果多个 TypedArray 实例共享同一个 ArrayBuffer,增删改查都共享。
const buffer = new ArrayBuffer(8);
const uint8Array1 = new Uint8Array(buffer);
const uint16Array = new Uint16Array(buffer);
uint8Array1[0] = 255;
console.log(uint16Array[0]); //255
作用
1. 操作缓冲区对象
为 ArrayBuffer 提供了一个视图,允许以特定的数据类型(如整数、浮点数)来访问和操作 ArrayBuffer 中的二进制数据。
2. 优化数字数组的存储空间
JS中所有的数字,均使用双精度浮点数保存,64位存一个number,存储10000个0需要10000*64/8/1024=78KB 这时如果改成 Int8Array 存储只需要9KB的存储空间即可。
3. 存储图像、音频等二进制文件的内容
const imageInput = document.getElementById('imageInput');
const displayImage = document.getElementById('displayImage');
imageInput.addEventListener('change', function () {
const file = this.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function (e) {
const arrayBuffer = e.target.result;
// 使用 Uint8Array 存储二进制数据
const uint8Array = new Uint8Array(arrayBuffer);
// 创建 Blob 对象
const blob = new Blob([uint8Array], { type: file.type });
// 创建临时 URL
const objectURL = URL.createObjectURL(blob);
// 显示图像
displayImage.src = objectURL;
};
reader.readAsArrayBuffer(file);
}
});
4. 结合序列化工具,提高数据传输速度
常结合 Protobuf FlatBuffer 一起使用, 将数据以二进制形式存储在内存中,利用三者的优势,可以显著提高数据处理和传输的效率。
flatBuffers官网
- 文件编译指令
flatc --js XXX.fbs - [flatbuffers编译器下载 (github.com)](github.com/google/flat…
- flatbuffers 编译器2.x版本之后不再支持js,先编译成ts再tsc为js或者直接用ts
- 复杂数据先将fbs文件对应的数据结构画出来 使用案例
//city.fbs文件
namespace city_struct;
table city_Struct{
name:string;
id:uint64;
data:[ubyte];
}
root_type city_struct;
include 'BeiDaSplitInfo.fbs'
table Beijing_Struct{
code:int64;
data:[ubyte];
school_info:SchoolInfo
}
table SchoolInfo{
cid:int32;
seq:int32;
num:int32;
compress:string;
}
//接收uint8Array
const bbd = new ByteBuffer(uint8Array)
const cityroot = city_Struct.getRootAsCity_Struct(bbd)//调用getRoot
console.log(cityRooot.name())//看转化的js文件里面的方法调
//ubyte作为内层对象getRoot的参数调
const bbd2 = new ByteBuffer(cityroot.dataArray())
//beijing里面的child_info类型是别的分片的别的fbs类型
//先获取beijingRoot和SchoolInfoInfo
const beijingRoot = Beijing_Struct.getRootAsBeijing_Struct(bbd2)
const schoolRoot = schoolRoot.getRoot(bbd2)//用BeijingRoot的参数
//beijing.js文件里面的school方法参数是SchoolObj,调用把两个root绑一起
beijingRoot.school(schoolRoot)
//学校太多了,分片数据汇总
const totalData = []
if(schoolRoot.num()===schoolRoot.seq()+1){
totalData.push(...beijingRoot.dataArray())//最后一个分片
}else{
totalData.push(...Array.from(beijingRoot.dataArray()))
}
//将所有分片信息用protobuf继续反序列化
const total = new uint8Array(totalData)
//protobuf只需要创建实例并deserializeBinary
const pblist = new sList.deserializeBinary(totalData)
console.log(pblist.toObject())
当存储的内容过大,后端常常会进行压缩,如果是zlib压缩,通常先将TypedArray进行压缩以后再解码
export const getCompressData = (data) => {
return new Promise((resolve, reject) => {
const zlib = require('zlib')
zlib.gunzip(Buffer.from(data), (err, res) => {
if (err) reject(err)
resolve(res)
})
})
}
Node 中的类型化数组 Buffer
- Buffer继承 Uinit8Array 属于类型化数组
- 下面的一个字母用一个字节存储
- Buffer 在使用和输出时可能需要用十六进制表示,下面的两位十六进制表示一个字节
- Buffer.from 用于从不同的数据源创建一个Buffer实例
const buffer = Buffer.from("abcdefg","utf-8")
console.log(buffer)
// <Buffer 61 62 63 64 65 66 67>
- 常用于处理服务器net http模块返回的二进制数据
const http = require("http");
const request = http.request(
"http://yuanjin.tech:5005/api/movie",
{
method: "GET",
},
(resp) => {
console.log(resp.statusCode);
//请求体要按Buffer块读取
let result = "";
resp.on("data", (chunk) => {
result += chunk.toString("utf-8");
console.log(chunk instanceof Buffer);
});
resp.on("end", () => {
console.log(JSON.parse(result));
});
}
);
request.end();
特性
仅存在于Node.js环境中,可以修改值
const buf = Buffer.from([1, 2, 3]);
buf[1] = 100;
console.log(buf); // 输出:<Buffer 01 64 03>
二进制与字符串之间转换方法
//将字符串转换成特定的编码
const buf = Buffer.from("你好", "utf8");//`utf8`、`ascii`、`base64`、`hex`
console.log(buf.toString("base64")); // 输出:5L2g5aW9
// 将二进制转换成字符串
const buffer = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // 二进制数据:Hello
// 使用 UTF-8 编码将二进制数据转换为字符串
const str = buffer.toString('utf8');
console.log(str); // 输出:Hello
ArrayBuffer 和 Buffer的相互转换
// ArrayBuffer -> Buffer
const arrayBuffer = new ArrayBuffer(3);
const buf = Buffer.from(arrayBuffer);
console.log(buf)
// Buffer -> ArrayBuffer
const buf = Buffer.from([1, 2, 3]);
const arrayBuffer = buf.buffer;
console.log(new Uint8Array(arrayBuffer)); // 输出:Uint8Array(3) [1, 2, 3]
三、DataView
定义
DataView 为 ArrayBuffer 提供了一个抽象的视图,允许你以不同的数据类型(如整数、浮点数等)、不同的字节序(大端序或小端序)来读取和写入 ArrayBuffer 中的数据,而无需考虑底层的字节表示。
实例化
DataView 构造函数的三个参数:
- buffer:必需,一个 ArrayBuffer 或 SharedArrayBuffer 对象,DataView 将基于该缓冲区进行操作。
- byteOffset:可选,指定从 ArrayBuffer 的哪个字节开始创建视图,默认值为 0。
- byteLength:可选,指定视图的字节长度,如果未指定,则视图将包含从 byteOffset 到 ArrayBuffer 末尾的所有字节。
// 创建一个长度为 16 字节的 ArrayBuffer
const buffer = new ArrayBuffer(16);
// 创建一个 DataView 实例,从第 4 个字节开始,长度为 8 字节
const dataView = new DataView(buffer, 4, 8);
//buffer属性
console.log(dataView.buffer === buffer); // 输出: true
//byteLength返回字节长度
console.log(dataView.byteLength); // 输出: 8
//byteOffset返回DataView在ArrayBuffer中的起始字节偏移量
console.log(dataView.byteOffset); // 输出: 4
写入和读取方法
byteOffset:指定从ArrayBuffer的哪个字节开始读取/写入数据。value:要写入的数据。littleEndian:可选,布尔值,指定是否使用小端序,默认值为false(即使用大端序)。 常见方法setInt8(byteOffset, value):写入一个 8 位有符号整数。setUint8(byteOffset, value):写入一个 8 位无符号整数。setInt16(byteOffset, value, littleEndian):写入一个 16 位有符号整数。setUint16(byteOffset, value, littleEndian):写入一个 16 位无符号整数。getInt8(byteOffset):读取一个 8 位有符号整数。getUint8(byteOffset):读取一个 8 位无符号整数。getInt16(byteOffset, littleEndian):读取一个 16 位有符号整数。getUint16(byteOffset, littleEndian):读取一个 16 位无符号整数。
四、Blob(Binary Large Object)
定义
Blob(Binary Large Object,二进制大对象) 是一个不可变的对象,用于表示可变数据量的二进制数据。它在 Web 开发中被广泛用于处理文件、图片、音频、视频等二进制资源。Blob 对象可以包含多种类型的数据,并且可以通过 FileReader、URL.createObjectURL() 或其他 API 进行读取和操作。
实例化
new Blob(array,options)
- array:字符串、
ArrayBuffer、TypedArray或其他 Blob 对象:e.target.files[0] || [string] || [arrayBuffer]] - props:MIME和endings Blob对象一旦创建不可修改
// 字符串Blob Array
Blob const text = 'Hello, World!';
const blob = new Blob([text], { type: 'text/plain' });
//TypedArray 内容的Blob
const buffer = new ArrayBuffer(8);
const uint8Array = new Uint8Array(buffer);
// 向数组中写入数据
for (let i = 0; i < uint8Array.length; i++) {
uint8Array[i] = i;
}
const blobFromArrayBuffer = new Blob([uint8Array], { type: 'application/octet-stream' });
//File是Blob对象的子类
<input type="file" id="fileInput">
<script>
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', function () {
const file = this.files[0];
if (file) {
const newBlob = new Blob([file], { type: file.type });
}
});
</script>
Blob对象的读取
1. FileReader
const blob = new Blob(['Hello, World!'], { type: 'text/plain' });
const reader = new FileReader();
reader.onload = function () {
const arrayBuffer = reader.result;
console.log(arrayBuffer);
};
//读取为`ArrayBuffer`,适合处理二进制数据,如图片、音频等
reader.readAsArrayBuffer(blob);
//读取为文本内容,可指定编码格式,默认是`UTF-8`
reader.readAsText(blob);
//读取为`data:` URL 格式的字符串,通常用于在网页中显示图片等
reader.readAsDataURL(blob);
2. 使用Response构造函数
创建一个Response对象,然后使用Response对象的方法来读取Blob内容
const blob = new Blob(['Hello, World!'], { type: 'text/plain' });
const response = new Response(blob);
response.text().then(result => {
console.log(result); //获取文本内容
});
//读取ArrayBuffer
response.arrayBuffer()
3. createObjectURL
通过URL.createObjectURL()方法可以创建一个指向Blob对象的临时 URL,然后可以通过该 URL 来访问Blob内容,常用于在img标签中显示图片或在a标签中实现文件下载等
const blob = new Blob(['Hello, World!'], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.textContent = '下载文件';
a.download = 'test.txt';
document.body.appendChild(a);
// 释放URL
URL.revokeObjectURL(url);
Blob与File的关系
File对象的完整原型链
file --> File.prototype --> Blob.prototype --> Object.prototype --> null
当访问 File 对象的属性或方法时,JavaScript 会先在 file 对象本身查找,如果找不到,会依次在 File.prototype、Blob.prototype、Object.prototype 中查找,直到找到该属性或方法,或者到达原型链的顶端(null)
// 创建一个 File 对象
const file = new File(['Hello, World!'], 'example.txt', { type: 'text/plain' });
// 检查 File 对象的直接原型
console.log(Object.getPrototypeOf(file) === File.prototype);
// 输出: true
// 检查 File 对象的原型链上是否包含 Blob.prototype
console.log(Object.getPrototypeOf(File.prototype) === Blob.prototype);
// 输出: true
五、FileReader
创建
构造函数无参 new FileReader()
在浏览器中,由于安全限制,不能直接用FileReader读取相对路径的文件,也不能使本地的相对路径的文件转换成Blob想要读取本地的文件只能通过用户交互。
读取步骤
在下列读取方法中写入Blob或者File,在onload中得到读取的结果,可以将读取结果进行以下处理
- readAsArrayBuffer()读取文件ArrayBuffer对象
- 可以使用new Uint8Array(result)来将arrayBuffer转换成指定类型的TypedArray
- readAsDataUrl()返回base64字符串
- readAsText()读取字符串
- readAsBinaryString()原始二进制文件
<body>
<input type="file" id="fileInput">
<script>
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', function (event) {
const file = event.target.files[0];
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
const arrayBuffer = reader.result;
const uint8Array = new Uint8Array(arrayBuffer);
console.log(uint8Array);
};
});
</script>
</body>
六、URL
定义
实例化
new URL(url, base),其中 url 是要解析的 URL 字符串,base 是可选的基本 URL,用于处理相对 URL
//单个参数
const url = new URL(location.href)
console.log(url)
//全部参数
const url = new URL('../cats', 'http://www.example.com/dogs');
console.log(url.toString()); // "www.example.com/cats"
console.log(url.pathname); // "/cats"
实例方法
toString():返回完整的 URL 字符串。toJSON():返回完整的 URL 字符串。
静态方法
URL.createObjectURL(object)
- 用于创建一个指向指定对象(如
Blob或File)的 URL。这个 URL 是一个全局唯一的字符串,类似于blob:https://example.com/unique-id,它允许网页在不将数据上传到服务器的情况下,直接访问本地文件或内存中的数据。 - 参数
object:可以是一个Blob对象、File对象,或者任何实现了Blob接口的对象。 - 返回一个字符串,表示指向对象的 URL。
使用场景
- 预览本地文件 在用户选择文件后,无需上传到服务器即可直接在页面中预览。
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
const objectURL = URL.createObjectURL(file);
const img = document.createElement('img');
img.src = objectURL;
document.body.appendChild(img);
});
- 动态生成文件并下载:
const blob = new Blob(["Hello, world!"], { type: "text/plain" });
const objectURL = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = objectURL;
a.download = "hello.txt";
a.click();
- 在
<audio>或<video>中播放本地文件:
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
const objectURL = URL.createObjectURL(file);
const audio = document.createElement('audio');
audio.src = objectURL;
audio.controls = true;
document.body.appendChild(audio);
});
类似可作为 src 属性的 Base64
和URL创建的objectURL类似,base64 也可以作为链接进行预览
创建方法
- btoa()将字符串转换成base64
const str = "Hello";
const base64 = btoa(str); // 输出:SGVsbG8=
console.log(base64);
- fileReader.readAsDataURL(blob|file)将二进制数据(如图片)转换为 Base64
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
const base64 = e.target.result.split(',')[1]; // 去掉数据类型前缀
console.log(base64);
};
reader.readAsDataURL(file);
});
- canvas.toDataURL()//构造器重载,参见.toDataURL()| MDN (mozilla.org),将Canvas转换成Base64实现效果预览
<!DOCTYPE html>
<html>
<head>
<style>
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="signatureCanvas" width="400" height="200"></canvas>
<button onclick="clearCanvas()">清除</button>
<button onclick="downloadSignature()">下载</button>
<img id="signatureImage" width="400" height="200">
<script>
const canvas = document.getElementById('signatureCanvas');
const ctx = canvas.getContext('2d');
const signatureImage = document.getElementById('signatureImage');
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
let drawing = false;
canvas.addEventListener('mousedown', (e) => {
drawing = true;
ctx.beginPath();
ctx.moveTo(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
});
canvas.addEventListener('mousemove', (e) => {
if (!drawing) return;
ctx.lineTo(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
ctx.stroke();
});
canvas.addEventListener('mouseup', () => {
drawing = false;
});
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
signatureImage.src = '';
}
function downloadSignature() {
const dataURL = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = dataURL;
link.download = 'signature.png';
signatureImage.src = canvas.toDataURL('image/png');
link.click();
}
</script>
</body>
</html>
URL.revokeObjectURL(objectURL)
- 用于撤销之前通过
URL.createObjectURL()创建的对象 URL。撤销后,该 URL 将不再有效,浏览器会释放与之关联的资源。 objectURL:需要撤销的对象 URL,必须是之前通过URL.createObjectURL()创建的。 两个静态方法经常搭配一块使用,避免内存资源的浪费
const fileInput = document.querySelector('input[type="file"]');
let currentObjectURL = null;
fileInput.addEventListener('change', (event) => {
if (currentObjectURL) {
URL.revokeObjectURL(currentObjectURL);
}
const file = event.target.files[0];
currentObjectURL = URL.createObjectURL(file);
const img = document.querySelector('img');
img.src = currentObjectURL;
});
七、websocket 的二进制传输
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它支持二进制数据的传输。可以通过 WebSocket 的 send() 方法发送 ArrayBuffer 或 Blob 类型的数据
// 创建一个 WebSocket 连接
const socket = new WebSocket('ws://example.com');
// 连接建立后发送二进制数据
socket.addEventListener('open', function () {
const buffer = new ArrayBuffer(16);
const uint8Array = new Uint8Array(buffer);
uint8Array[0] = 255;
socket.send(buffer);
}
);
// 接收二进制数据
socket.addEventListener('message', function (event) {
const arrayBuffer = event.data;
console.log(arrayBuffer);
}
);