引言: 近期,工作中发现,有些前端小伙伴很少接触到二进制数据,所以将项目中二进制数据的应用和大家分享一下,适合入门了解,高手慎入,欢迎拍砖。项目背景: 基于公司原有图形处理的二进制数据文件(公司自定义的二进制数据格式),实现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. 数据读取ArrayBuffer
和DataView
:
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. 数据解压TypedArray
和pako.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