JS - 使用ArrayBuffer加载二进制文件数据(附:float数组生成二进制bin文件)
作者:hangge | 2020-03-27 08:10
随着 HTML5 功能的丰富,现在前端展示的数据变得越来越原来越庞大,甚至会有上万条数据的传输。如果还用传统的 json、xml 这种形式,数据量稍大一些就难堪重任了。
要提高大数据的传输性能,那么数据通信就必须是二进制的。我们可以在 XMLHttpRequest 请求中使用 ArrayBuffer 方式,和服务器进行二进制的传输。
一、ArrayBuffer 与类型化数组
1,ArrayBuffer 介绍
(1)ArrayBuffer 的应用特别广泛,无论是 WebSocket、WebAudio 还是 Ajax 等等,前端方面只要是处理大数据或者想提高数据处理性能,那一定是少不了 ArrayBuffer 。
(2)ArrayBuffer 是一段连续的长度固定的字节序列,如:通过实例化 ArrayBuffer 对象在内存中创建一段二进制存储空间(或叫二进制缓冲区)。
注意:由于是连续的内存空间,故在其上进行的读写操作都会比普通 JS Array 快很多。
// 创建一段字节长度为8的内存空间 var buffer = new ArrayBuffer(8); // 获取字节长度 console.log(buffer.byteLength); // 8
(3)XMLHttpRequest Level 2 简称 XHR2。相较于老版本的 XHR 只支持文本数据的传送,XHR2 还可以用来读取和上传任意类型的数据:
- 通过 xhr.responseType 可以是设置响应的数据格式,可选参数有:“text”、“arraybuffer”、“blob”或“document”。对应的返回数据为 DOMString、ArrayBuffer、Blob、Document;默认参数为"text"。
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
buffer = xhr.response;
console.log(buffer)
};
xhr.send();
2,类型化数组(Type Array)
(1)类型化数组是读写 ArrayBuffer 中数据的接口,JS 可以通过 8 种不同的接口创建类型化数组,分别为:| 类型 | 大小(字节) | 描述 | Web IDL type | C语言等效类型 | Java等效类型 |
Int8Array |
1 | 8位二进制带符号整数 -2^7~(2^7) - 1 | byte | int8_t | byte |
Uint8Array |
1 | 8位无符号整数 0~(2^8) - 1 | octet | uint8_t | |
Int16Array |
2 | 16位二进制带符号整数 -2^15~(2^15)-1 | short |
int16_t | short |
Uint16Array |
2 | 16位无符号整数 0~(2^16) - 1 | unsigned short |
uint16_t | |
Int32Array |
4 | 32位二进制带符号整数 -2^31~(2^31)-1 | long | int32_t | int |
Uint32Array |
4 | 32位无符号整数 0~(2^32) - 1 | unsigned int |
uint32_t | |
Float32Array |
4 | 32位IEEE浮点数 | unrestricted float | float | float |
Float64Array |
8 | 64位IEEE浮点数 | unrestricted double | double | doubl |
(2)如果 ArrayBuffer 里面是单一类型数据,可用对应的类型化数组直接进行解析:
var array = new Uint8Array(buffer);
for(var i = 0; i < array.length; ++i){
console.log(array[i])
}
(3)如果 ArrayBuffer 里面是混编类型数据,需要用移位的方式解析其中对应类型的数据:
var array = new Uint8Array(buffer [, byteOffset [, length]])
for(var i = 0; i < array.length; ++i){
console.log(array[i])
}
二、 后台生成二进制 bin 文件
1,使用 Java 生成
(1)下面代码将一个数组以二进制的形式写到一个 bin 文件中:
public class Test {
public static void main(String[] args) {
// 待写入文件的数据
float[] fbuf = {161.2f, 51.6f, 167.5f, 59.0f, 159.5f, 49.2f, 157.0f, 63.0f, 155.8f, 53.6f};
// 开始写入
try (FileOutputStream fos = new FileOutputStream("/Volumes/BOOTCAMP/data.bin")) {
for(float f : fbuf){
byte[] bs = getBytes(f);
fos.write(bs);
}
fos.flush();
fos.close();//为了节省IO流的开销,需要关闭
System.out.println("文件生成成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
// 将 float 转成 byte[]
public static byte[] getBytes(float data) {
int intBits = Float.floatToIntBits(data);
return getBytes(intBits);
}
public static byte[] getBytes(int data) {
byte[] bytes = new byte[4];
bytes[0] = (byte) (data & 0xff);
bytes[1] = (byte) ((data & 0xff00) >> 8);
bytes[2] = (byte) ((data & 0xff0000) >> 16);
bytes[3] = (byte) ((data & 0xff000000) >> 24);
return bytes;
}
}
(2)代码执行后可以看到 data.bin 文件生成成功。

2,使用 Node.js 生成
(1)创建一个 create.js 文件,内容如下:
(2)接着执行如下命令,执行完毕后同样会生成一个 data.bin 文件,内容同上面 Java 方式生成的是一样的:
(2)运行结果如下:
const fs = require('fs')
const buf = new ArrayBuffer(40) // 创建一个 40 bytes 的缓存区
const float32Arr = new Float32Array(buf) // type=float32 (4 Bytes) array view
const arr = [161.2, 51.6, 167.5, 59.0, 159.5, 49.2, 157.0, 63.0, 155.8, 53.6];
arr.forEach((val, i) => {
float32Arr[i] = val
})
fs.writeFileSync('data.bin', new Buffer(buf)); // data.bin
console.log("文件生成成功!");
node create.js
三、 前台读取二进制 bin 文件
1,基本用法
(1)下面代码使用 ArrayBuffer 方式加载 data.bin 文件,然后使用 Float32Array 读取里面的数据,最后遍历输出数组里的所有浮点数。
var dataURL = 'http://localhost:8080/uploadFile/data.bin';
var xhr = new XMLHttpRequest();
xhr.open('GET', dataURL, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
var rawData = new Float32Array(this.response);
for (var i = 0; i < rawData.length; i++) {
console.log(rawData[i]);
}
}
xhr.send();
(2)运行结果如下:

2,在 ECharts 上的应用
(1)当使用 ECharts 绘制坐标轴散点图或者地图散点图时,如果数据量十分的庞大,就可以使用二进制的方式来传输散点数据,从而减少数据传输时间以及数据解析转换时间,提高效率:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ECharts</title>
<!-- 引入 echarts.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/4.2.1/echarts.js"></script>
</head>
<body>
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
// 图标配置
var option = {
xAxis: {
scale: true
},
yAxis: {
scale: true
},
tooltip: {
formatter: '{c}'
},
series: [
{
type: 'scatter',
data: [],
}]
};
// 加载散点数据
var dataURL = 'http://localhost:8080/uploadFile/data.bin';
var xhr = new XMLHttpRequest();
xhr.open('GET', dataURL, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function (e) {
// 将原属数据转成图表使用的数据:[a,b,c,d,e,f] => [[a,b],[c,d],[e,f]]
var rawData = new Float32Array(this.response);
var data = new Array();
for (var i = 0; i < rawData.length; i += 2) {
data.push([rawData[i], rawData[i+1]]);
}
// 使用刚指定的配置项和数据显示图表。
option.series[0].data = data;
myChart.setOption(option);
}
xhr.send();
</script>
</body>
</html>
(2)效果如下:

附:浮点数精度问题解决
1,问题描述
从前面的运行结果可以发现,当 Java 端将 float 以二进制的形式写入文件,js 再从中读取会产生精度问题。比如第一个原始数据是 161.2,但 js 读取出来就变成了 161.1999969482422

2,解决办法
(1)如果使用传统的数据传输方式,为避免这个问题,后台通常是将 float 转成 String 后再传输到前台。
(2)如果以二进制形式传输的话,可以先将 float 乘以 10 的 7 次方(因为 float 精度为 7 位)转成 int 类型,前台 js 以 int 类型接收数据,再除以 10000000 还原成原始数据。
3,样例代码
(1)后台将 float 类型的原始数据放大 10000000 倍写入二进制文件:
注意:为了确保精度计算完全准确,我们不能直接将 float 乘以 10000000,而是需要借着 BigDecimal 来计算。
public class Test {
public static void main(String[] args) {
// 待写入文件的数据
float[] fbuf = {161.2f, 51.6f, 167.5f, 59.0f, 159.5f, 49.2f, 157.0f, 63.0f, 155.8f, 53.6f};
// 开始写入
try (FileOutputStream fos = new FileOutputStream("/Volumes/BOOTCAMP/data.bin")) {
for(float f : fbuf){
//将 float * 10的7次方 转成int型
BigDecimal a = new BigDecimal(Float.toString(f));
BigDecimal b = new BigDecimal(Integer.toString(10000000));
int i = a.multiply(b).intValue();
byte[] bs = getBytes(i);
fos.write(bs);
}
fos.flush();
fos.close();//为了节省IO流的开销,需要关闭
System.out.println("文件生成成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
// 将 float 转成 byte[]
public static byte[] getBytes(float data) {
int intBits = Float.floatToIntBits(data);
return getBytes(intBits);
}
public static byte[] getBytes(int data) {
byte[] bytes = new byte[4];
bytes[0] = (byte) (data & 0xff);
bytes[1] = (byte) ((data & 0xff00) >> 8);
bytes[2] = (byte) ((data & 0xff0000) >> 16);
bytes[3] = (byte) ((data & 0xff000000) >> 24);
return bytes;
}
}
(2)前台 js 使用 Int32Array 读取数据,然后再除以 10 的 7 次方的到原始数据:
注意:js 中 1e7 表示 10 的 7 次方,即 10000000
var dataURL = 'http://localhost:8080/uploadFile/data.bin';
var xhr = new XMLHttpRequest();
xhr.open('GET', dataURL, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
var rawData = new Int32Array(this.response);
for (var i = 0; i < rawData.length; i++) {
console.log(rawData[i] / 1e7);
}
}
xhr.send();
(3)运行结果如下,可以看到这次就没有浮点数精度问题了:
全部评论(0)