前端进阶:二进制数据的操控----附项目代码

前端进阶:二进制数据的操控----附项目代码

引言: 近期,工作中发现,有些前端小伙伴很少接触到二进制数据,所以将项目中二进制数据的应用和大家分享一下,适合入门了解,高手慎入,欢迎拍砖。

项目背景: 基于公司原有图形处理的二进制数据文件(公司自定义的二进制数据格式),实现Canvas绘图。

话说:项目开始的时候我也是一脸懵逼,这么多技术难点需要攻克,1. 如何请求二进制数据流?2. 如何解压二进制数据?3. 如何读取二进制数据?……


接下来我们一一攻破:见招拆招

1. 数据请求arraybuffer

基于ajax请求,设置接受的数据格式为arraybuffer类型,基于流文件的读取是需要异步来处理的,否则数据可能有丢失。

  let oReq = null;
  if (window.XMLHttpRequest) {
    oReq = new XMLHttpRequest();
  }
  else {
    oReq = new ActiveXObject('Microsoft.XMLHTTP');
  }

  oReq.onprogress = this.updateProgress;//下载进度;
  oReq.responseType = "arraybuffer";
  oReq.onload = function () {
  // 数据下载完成会触发;
    if ((oReq.status >= 200 && oReq.status < 300) || oReq.status == 304) {
      var arrayBuffer = oReq.response;
      // 接下来的任务
      ReadFromByteArray(arrayBuffer); //读取收到的数据
    }
    if (oReq.status === 404) {
      alert("找不到对应文件!")
    }
  };

  oReq.open("GET", reqUrl, true);
  oReq.send(null);

2. 数据读取ArrayBufferDataView

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 不能直接操作,而是要通过类型数组对象或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。

2.1 校验数据的大小

function ReadFromByteArray(buffer){
  parseInt(buffer.byteLength / 1024);//文件大小,单位KB;
  
  if (buffer.byteLength < 64) {
     // 失败
     console.log("文件格式不对:长度小于64");
     return false;
     } else {
     // 成功 解压数据
   }
}
DataView 视图是一个可以从 ArrayBuffer 对象中读写多种数值类型的底层接口,在读写时不用考虑平台字节序问题。

接下来我们可以创建一个DataView 对象实例,此方法适合顺序存储的数据读取,非顺序(如增量式存储的数据不能按顺序读取,需要安装table中索引读取,否则会读错)。js提供了基本的二进制读取API,为了不用手动计算偏移量,我们可以对基础API进行封装,

DataView常用读取数据的API
getFloat32()
getFloat64()
getInt16()
getInt32()
getInt8()
getUint16()
getUint32()
getUint8()

2.2 校验文件名称

let dataView = new DataView(buffer, 0); //将上面获取的buffer传入到视图中
let headstr = headerFiler.ReadUTFBytes(5);//读取5个UTF8字节,结果为文件格式

if (headstr != "DWG") { 
//DWG为文件的格式,存放在数据结构的头部
 return false;
}else{
// 继续读取数据

}
       

3. 数据解压TypedArraypako.js

一个TypedArray 对象描述一个底层的二进制数据缓存区的一个类似数组(array-like)视图。事实上,没有名为 TypedArray的全局对象,也没有一个名为的 TypedArray构造函数。相反,有许多不同的全局对象,下面会列出这些针对特定元素类型的类型化数组的构造函数。在下面的页面中,你会找到一些不管什么类型都公用的属性和方法。

为了减少数据的传入,后端会对二进制数据进行压缩,前端难道要手写解压代码?就算你敢写,你是否敢用?当然寻找三方插件,关于js二进制数据的解压插件还真不多,我选用了pako.js,移动端暂为发现严重兼容性问题,PC端(IE)存在,一定慎用。期待推荐更加三方。

3.1 创建TypedArray

先将buffer转换为类型数组TypedArray,以便读取和操控。

let compressdata = new Uint8Array(buffer, byteOffset, length);//把上面获取`buffer`转换成可操控的`TypedArray`。创建一个无符号整型的TypedArray,偏移量为byteOffset,长度为length。
Tips:偏移量为byteOffset类似于数组的索引,默认为0, 设置后,从此开始读取。如:
const compressdata = new Uint8Array(buffer, 4, 10);//从第4个字节开始读取,长度为10个字节

3.2 解压数据

利用pako.js解压数据

let uncompress = pako.inflate(compressdata);//解压数据;
let uncompressdata = uncompress.buffer;// ArrayBuffer {}
let dataViewData = new DataView(uncompressdata, 0);//解压后数据;
Tips:js中的number数据类型,无论数字的大小,都将占用8个字节,即64位,就是Java 中double类型的长度;1字符串会占用2字节,即16位。
js中此种规定,省去了我们声明变量时对数据大小的计算,方便使用,但是,这样就会造成浪费大量的存储空间,明显增大数据的大小。及其不便于大数据的传输,所以会对数据进行压缩。

封装数据读取的API,避免手动计算偏移量

function WsFiler(dataView) {
    this.dataView = dataView;
    this.dataView.position = 0;
}

WsFiler.SEEK_BEGIN = 0;
WsFiler.SEEK_SET = 0;
WsFiler.SEEK_CUR = 1;
WsFiler.SEEK_END = 2;

WsFiler.prototype.ReadByte = function () {
    var b = this.dataView.getUint8(this.dataView.position);
    this.dataView.position++;
    return b;
}

WsFiler.prototype.ReadShort = function () {
    var s = this.dataView.getInt16(this.dataView.position, true);
    this.dataView.position += 2;
    return s;
};

WsFiler.prototype.ReadInt32 = function () {
    var int32 = this.dataView.getInt32(this.dataView.position, true);
    this.dataView.position += 4;
    return int32;
};

WsFiler.prototype.ReadUInt32 = function () {
    var uint32 = this.dataView.getUint32(this.dataView.position, true);
    this.dataView.position += 4;
    return uint32;
}
WsFiler.prototype.ReadUtf8String = function () {
    var len = this.ReadInt32();//字符串长度;

    return this.ReadUTFBytes(len);
};

WsFiler.prototype.ReadFloat = function () {
    var ret = this.dataView.getFloat32(this.dataView.position, true);
    this.dataView.position += 4;
    return ret;
};

WsFiler.prototype.ReadDouble = function () {
    var ret = this.dataView.getFloat64(this.dataView.position, true);
    this.dataView.position += 8;
    return ret;
}

4. 数据存储:

读取到的数据可以任意操控,可以创建一个数组进行存储。便于我们的后续操控。数据的存储就相对简单了,根据需要将数据拆分即可。


这样,我们就完成了二进制数据的请求、解压、读取和存储了。

后续继续分享,用canvas把我们读到的数据画到网页上。

欢迎大家拍砖。不胜感谢!

参考文献:MDN

  1. https://developer.mozilla.org...
  2. https://developer.mozilla.org...
  3. https://developer.mozilla.org...
上一篇:javascript-如何处理dataview内部项目的mouseclicks


下一篇:筛选DataTable中的空字符串