博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Web 前端开发日志(二):JavaScript 的二进制操作
阅读量:6212 次
发布时间:2019-06-21

本文共 4567 字,大约阅读时间需要 15 分钟。

文章为在下以前开发时的一些记录与当时的思考, 学习之初的内容总会有所考虑不周, 如果出错还请多多指教.

TL;DR

在浏览器中处理二进制数据,需要使用 Typed ArrayArrayBufferDataView.

二进制数据使用的数据类型:Typed Array

在浏览器环境中使用的二进制数据类型一般为 Typed Array(类型数组) ,它和普通的数组很像,只不过里面的成员类型是严格要求,并且长度固定的.

类型数组拥有以下几种:

  • Int8Array:每个成员是有符号的 8 位整形,取值范围 -128 - 127.
  • Uint8Array:每个成员是无符号的 8 位整形,取值范围 0 - 255.
  • Uint8ClampedArray:每个成员是无符号的 8 位整形,取值范围 0 - 255,和上面的类型不同的是,若成员超过 255 或小于 0,则取相应最大值 255 或 最小值 0,而 Uint8Array 会进行类推取一个越界后的映射值. 当在处理色彩相关逻辑时非常有用.
  • Int16Array:每个成员为有符号的 16 位整形,取值范围 -32768 - 32767.
  • Uint16Array:每个成员为无符号的 16 位整形,取值范围 0 - 65535.
  • Int32Array:每个成员为有符号的 32 位整形,取值范围 -2147483648 - 2147483647.
  • Uint32Array:每个成员为无符号的 32 位整形,取值范围 0 - 4294967295.
  • Float32Array:浮点数版本的 Int32Array.
  • Float64Array:64 位版本的 Float32Array.

简单举例:

const uInt8Array = new Uint8Array(10)uInt8Array.length  // 10uInt8Array[0] = 255  // 可以操作下标.复制代码

类型数组的详细文档您可以在查阅.

存放数据的容器:ArrayBuffer

一个类型数组是需要存放到一个容器中的,这个容器叫做 ArrayBuffer.

ArrayBuffer 用来向浏览器申请一块区域存放类型数组,作用有点像 malloc 的感觉.

创建类型数组时可以先创建一个 ArrayBuffer 然后传入,也可以直接创建指定长度的类型数组;如果直接创建,则浏览器会自动创建一个 ArrayBuffer 来存储此类型数组:

const int8 = new Int8Array(10)int8.buffer  // 这个就是存储这个类型数组的 ArrayBuffer.// 当然也可以显式创建:const buffer = new ArrayBuffer(10)  // 申请 10 字节长度.const int8 = new Int8Array(buffer)复制代码

ArrayBuffer 的详细说明请看.

方便操作二进制数据的工具:DataView

实际上类型数组可以使用下标的方式来读写数组,只不过,太痛苦了点吧……还有大小端问题……

因此对于复杂的逻辑,我们可以使用 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 上使用它的话是就要使用二进制方式传输数据,而非文本,所以数据包是需要在浏览器进行拼接的.

根据 的文档可以看到其数据包格式:

四个字节表示包长度,两个字节表示头部长度,两个字节表示协议版本,四个字节表示当前操作,四个字节为顺序 ID 标记,剩下的为数据本体.

那么就可以写一个简单的创建代码:

// 根据文档定义 offset.const packetOffset = 0const headerOffset = 4const verOffset = 6const opOffset = 8const seqOffset = 12const 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)复制代码

转载地址:http://hbpja.baihongyu.com/

你可能感兴趣的文章
Spring boot for Eclipse 开发指南第四节 Spring-Security
查看>>
mysql 语句优化
查看>>
linux安装软件
查看>>
Swift与Cocoa框架开发
查看>>
Pull-To-Refresh TableView
查看>>
flash light
查看>>
FGallery
查看>>
GCPlaceholderTextView
查看>>
PullableView
查看>>
解决Cannot change version of project facet Dynamic w
查看>>
开源 java CMS - FreeCMS2.3 会员权限管理
查看>>
UITableView简单使用---添加背景
查看>>
IOS引导界面
查看>>
整合SSH备忘录
查看>>
互联网舆情监测开发平台
查看>>
设计模式-工厂模式(Factory)
查看>>
Django对静态文件的处理——部署阶段
查看>>
img src 中文路径解决办法
查看>>
centos系统sudo命令配置
查看>>
PMP 管理学6大定律之一(墨菲定律)
查看>>