文章为在下之前开发时的一些记录与当时的思考, 学习之初的内容总会有所考虑不周, 若是出错还请多多指教.git
在浏览器中处理二进制数据,须要使用 Typed Array
、ArrayBuffer
、DataView
.github
在浏览器环境中使用的二进制数据类型通常为 Typed Array(类型数组)
,它和普通的数组很像,只不过里面的成员类型是严格要求,而且长度固定的.typescript
类型数组拥有如下几种:canvas
简单举例:数组
const uInt8Array = new Uint8Array(10)
uInt8Array.length // 10
uInt8Array[0] = 255 // 能够操做下标.
复制代码
类型数组的详细文档您能够在这里查阅.浏览器
一个类型数组是须要存放到一个容器中的,这个容器叫作 ArrayBuffer
.工具
ArrayBuffer
用来向浏览器申请一块区域存放类型数组,做用有点像 malloc
的感受.学习
建立类型数组时能够先建立一个 ArrayBuffer
而后传入,也能够直接建立指定长度的类型数组;若是直接建立,则浏览器会自动建立一个 ArrayBuffer
来存储此类型数组:ui
const int8 = new Int8Array(10)
int8.buffer // 这个就是存储这个类型数组的 ArrayBuffer.
// 固然也能够显式建立:
const buffer = new ArrayBuffer(10) // 申请 10 字节长度.
const int8 = new Int8Array(buffer)
复制代码
ArrayBuffer
的详细说明请看这里.spa
实际上类型数组可使用下标的方式来读写数组,只不过,太痛苦了点吧……还有大小端问题……
所以对于复杂的逻辑,咱们可使用 DataView
这个对象来对类型数组进行操做:
const buffer = new ArrayBuffer(16)
const dataView = new DataView(buffer, 0)
dataView.setInt16(2, 20) // 在第二个 16 位数的位置上以大端写入 20.
dataView.getInt16(2) // 20
// 将 buffer 数据映射至一个 Int8Array 中查看 buffer 结构:
const int8Array = new Int8Array(buffer) // 类型数组也能够传入一个 ArrayBuffer 来建立,将直接映射这个 ArrayBuffer.
console.log(int8Array) // [0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// 以小端的方式再写一次:
dataView.setInt16(2, 20, true)
console.log(int8Array) // [0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
复制代码
DataView
提供了一些 API,详细的还请各位慢慢查阅文档.
goim 是 B 站搞的一个弹幕协议,在 WebSocket 上一样能够根据此协议进行数据设计,不过在 WebSocket 上使用它的话是就要使用二进制方式传输数据,而非文本,因此数据包是须要在浏览器进行拼接的.
根据 Github 的文档能够看到其数据包格式:
四个字节表示包长度,两个字节表示头部长度,两个字节表示协议版本,四个字节表示当前操做,四个字节为顺序 ID 标记,剩下的为数据本体.
那么就能够写一个简单的建立代码:
// 根据文档定义 offset.
const packetOffset = 0
const headerOffset = 4
const verOffset = 6
const opOffset = 8
const seqOffset = 12
const bodyOffset = 16
// 弹幕协议包头的基础长度为 16.
const headerLength = 16
/** * 建立一个数据包. * * @param {IPacketOption} option * @returns {ArrayBuffer} */
function createPacket (option: IPacketOption): ArrayBuffer {
const headerBuffer = new ArrayBuffer(headerLength)
const headerView = new DataView(headerBuffer, 0)
const bodyBuffer = stringToArrayBuffer(option.body)
headerView.setInt32(packetOffset, headerLength + bodyBuffer.byteLength) // 设置包长度, 长度 4 字节。
headerView.setInt16(headerOffset, headerLength) // 设置头部度. 4 字节.
headerView.setInt16(verOffset, option.version) // 设置版本. 2 字节.
headerView.setInt32(opOffset, option.operation) // 设置操做标识符, 4 字节.
headerView.setInt32(seqOffset, option.sequence) // 设置序列号, 4 字节.
return mergeArrayBuffer(headerBuffer, bodyBuffer)
}
/** * Packet 建立参数. * * @interface IPacketOption */
interface IPacketOption {
version: number
operation: number
sequence: number
body: string
}
/** * 将字符串转换为基于 Int8Array 的 ArrayBuffer. * * @param {string} content * @returns {ArrayBuffer} */
function stringToArrayBuffer (content: string): ArrayBuffer {
const buffer = new ArrayBuffer(content.length)
const bufferView = new Int8Array(buffer)
for (let i = 0, length = content.length; i < length; i++) {
bufferView[i] = content.charCodeAt(i)
}
return buffer
}
/** * 合并多个 ArrayBuffer 至同一个 ArrayBuffer 中. * * @param {...ArrayBuffer[]} arrayBuffers * @returns {ArrayBuffer} */
function mergeArrayBuffer (...arrayBuffers: ArrayBuffer[]): ArrayBuffer {
let totalLength = 0
arrayBuffers.forEach(item => {
totalLength += item.byteLength
})
const result = new Int8Array(totalLength)
let offset = 0
arrayBuffers.forEach(item => {
result.set(new Int8Array(item), offset)
offset += item.byteLength
})
return result.buffer
}
复制代码
这里有一段好像能够减小操做?
// 这段代码的大体意思是将 "存储了像素信息的数组中的数据绘制在 Canvas 中".
const buffer = new ArrayBuffer(imageData.data.length)
const buffer8 = new Uint8ClampedArray(buffer)
const data = new Uint32Array(buffer)
for (let y = 0; y < canvasHeight; y++) {
for (let x = 0; x < canvasWidth; x++) {
if (typeof pixelArr[y] === 'undefined') { continue }
const value = pixelArr[y][x]
if (typeof value === 'undefined' || value === null) { continue }
data[y * canvasWidth + x] =
255 << 24 |
value[2] << 16 |
value[1] << 8 |
value[0]
}
}
imageData.data.set(buffer8)
context.putImageData(imageData, 0, 0)
复制代码