hanniuniu 2019-06-27
引言: 近期,工作中发现,有些前端小伙伴很少接触到二进制数据,所以将项目中二进制数据的应用和大家分享一下,适合入门了解,高手慎入,欢迎拍砖。项目背景: 基于公司原有图形处理的二进制数据文件(公司自定义的二进制数据格式),实现Canvas绘图。
话说:项目开始的时候我也是一脸懵逼,这么多技术难点需要攻克,1. 如何请求二进制数据流?2. 如何解压二进制数据?3. 如何读取二进制数据?……
接下来我们一一攻破:见招拆招
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);
arraybuffer
和DataView
:arraybuffer
对象用来表示通用的、固定长度的原始二进制数据缓冲区。arraybuffer
不能直接操作,而是要通过类型数组对象或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
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()
let dataView = new DataView(buffer, 0); //将上面获取的buffer传入到视图中 let headstr = headerFiler.ReadUTFBytes(5);//读取5个UTF8字节,结果为文件格式 if (headstr != "DWG") { //DWG为文件的格式,存放在数据结构的头部 return false; }else{ // 继续读取数据 }
TypedArray
和pako.js
:一个TypedArray
对象描述一个底层的二进制数据缓存区的一个类似数组(array-like)视图。事实上,没有名为TypedArray
的全局对象,也没有一个名为的TypedArray
构造函数。相反,有许多不同的全局对象,下面会列出这些针对特定元素类型的类型化数组的构造函数。在下面的页面中,你会找到一些不管什么类型都公用的属性和方法。
为了减少数据的传入,后端会对二进制数据进行压缩,前端难道要手写解压代码?就算你敢写,你是否敢用?当然寻找三方插件,关于js二进制数据的解压插件还真不多,我选用了pako.js
,移动端暂为发现严重兼容性问题,PC端(IE)存在,一定慎用。期待推荐更加三方。
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个字节
利用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; }
读取到的数据可以任意操控,可以创建一个数组进行存储。便于我们的后续操控。数据的存储就相对简单了,根据需要将数据拆分即可。
这样,我们就完成了二进制数据的请求、解压、读取和存储了。后续继续分享,用canvas把我们读到的数据画到网页上。
欢迎大家拍砖。不胜感谢!
参考文献:MDN
responseType: ‘arraybuffer‘, //ArrayBuffer涉及面比较广,我的理解是ArrayBuffer代表内存之中的一段二进制数据,一旦生成不能再改。可以通过视图进行操作。