ArrayBuffer和Blob都是JavaScript中的二进制数据类型,但它们之间有一些区别。ArrayBuffer是一种存储二进制数据的类型,它可以被视为一段连续的内存区域,可以用来存储任何类型的二进制数据。Blob则是一种用于表示二进制数据的不可变对象,它一般用于处理文件数据等场景。因此,Blob对象通常是由一个或多个ArrayBuffer对象组成的数组,每个ArrayBuffer对象都包含文件数据的一部分。
API介绍
1、ArrayBuffer:用来表示通用的、固定长度的原始二进制数据缓冲区。它不能直接读写,只能通过视图(TypedArray
视图和 DataView
视图)来读写,视图的作用是以指定格式解读二进制数据。
2、TypedArray:一个 TypedArray 对象描述了底层二进制数据缓冲区的类数组视图
3、DataView
:DataView
视图是一个可以从二进制 ArrayBuffer 对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序(endianness)问题。
4、getUint8()
方法从 DataView相对于起始位置偏移 n 个字节处开始,获取一个无符号的 8-bit 整数 (一个字节)
5、getUint16()
方法从 DataView 相对于起始位置偏移 n 个字节处开始,获取一个 16-bit 数 (无符号短整型,2 个字节)
6、getUint32()
方法从 DataView 相对于起始位置偏移 n 个字节处开始,获取一个 32-bit 数 (无符号长整型,4 个字节)
7、setInt8()
从 DataView 起始位置以 byte 为计数的指定偏移量 (byteOffset) 处储存一个 8-bit 数 (一个字节)
8、setUint16()
从 DataView 起始位置以 byte 为计数的指定偏移量 (byteOffset) 处储存一个 16-bit 数 (无符号短整型)
9、setUint32()
从 DataView 起始位置以 byte 为计数的指定偏移量 (byteOffset) 处储存一个 32-bit 数 (无符号长整型)
10、Blob
:对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。
项目使用场景示例
开发小程序蓝牙传输项目中,接收数据和发送数据的处理。
1、10进制转16进制转二进制数据处理
2、解析二进制数据流为16进制/10进制
1、$command_tobuffer, // 将字符转化为buffer
/**
* 命令帧
* @typedef {object} json
* @property {Hex} head
* @property {Hex} end
* @property {Number} length
* @property {Hex} command
* @property {String|Number} code
* @returns ArrayBuffer
*/
function $command_tobuffer({ head = 0x55, end = 0x16, length, command, body, type='default' }) {
const byteOffset = 3; // 帧头 + 帧长度 + 命令
const buffer = new ArrayBuffer(byteOffset + length + 1); // byteOffset - 1 + length + 校验值 + 帧尾
const dataView = new DataView(buffer);
dataView.setUint8(0, head); // 帧头
dataView.setUint8(1, '0x' + length.toString(16)); // 帧长
dataView.setUint8(2, command); // 命令字
// 主体信息
if(type==='default'){
string2HexFromDataView({
dataView,
string: body,
byteOffset
});
} else {
number2HexForDataView({
dataView,
number: body,
byteOffset
});
}
const checkCodeIdx = length + 2;
const sign = checkSum(dataView, 2, checkCodeIdx);
// console.log('sum', sign);
dataView.setUint8(checkCodeIdx, '0x' + sign); // 检验码
dataView.setUint8(checkCodeIdx + 1, end); // 帧尾
return {buffer, checkSum:sign};
}
/**
* 字符串转16进制
* @typedef {object} json
* @property {DataView} dataView
* @property {String} string
* @property {Number} byteOffset
*/
function string2HexFromDataView({ dataView, string, byteOffset = 0 }) {
for (let i = 0; i < string.length; i++) {
const idx = i + byteOffset;
// console.log(string[i].charCodeAt(0).toString(16))
dataView.setUint8(idx, '0x' + string[i].charCodeAt(0).toString(16))
}
}
/**
* 数值(10进制)转16进制buffer 为了容 时间戳
*/
function number2HexForDataView({ dataView, number, byteOffset = 0 }) {
const hex = number.toString(16);
for (let i = 0; i < hex.length; i+=2) {
const idx = i / 2 + byteOffset;
dataView.setUint8(idx, '0x' + hex.substr(i, 2))
}
}
/**
* 检验和
* @param {DataView} dataView
* @param {Number} start
* @param {Number} end
* @returns Hex
*/
function checkSum(dataView, start, end) {
let sum = 0;
for (let i = start; i <= end; i++) {
const view = dataView.getUint8(i);
sum += view;
}
// console.log('ss', sum)
// return (('0x' + sum.toString(16)) & 0xFF).toString(16); // 与运算取8位
const resHex = (('0x' + sum.toString(16)) & 0xFF).toString(16);
return resHex.length === 1? 0+''+ resHex: resHex;
}
一个完整的传输帧包括如:帧头+帧长度+帧命令+帧内容+和校验+帧尾;
例如 dataView.setUint8(0, head) :dataView视图设置0的位置是由16进制标识0x开头的4位字符。以此类推最终拼接成一个完整的ArrayBuffer通过蓝牙传输出去。
2、$command_parsebuffer // 解析buffer
/**
* 转化进制并比补位
* @param {Number} val
* @param {Number} radix
* @param {*} length
* @returns String
*/
function toStringPad(val, radix = 10, length = 2) {
// padStart 补位
return val.toString(radix).padStart(length, 0);
}
function $command_parsebuffer(buffer) {
const dataView = new DataView(buffer);
const head = toStringPad(dataView.getUint8(0), 16);
const length = dataView.getUint8(1);
const command = toStringPad(dataView.getUint8(2), 16);
const end = toStringPad(dataView.getUint8(length + 3), 16);
const sign = toStringPad(dataView.getUint8(length + 2), 16);
const byteOffset = 3;
let body = '';
for (let i = 0; i < length - 1; i++) {
const view = toStringPad(dataView.getUint8(i + byteOffset), 16);
body += view;
}
return {
head,
length: toStringPad(length, 16),
command,
body,
sign,
end,
};
}
从蓝牙接口获取的buffer通过DataView视图获取出来,根据硬件给出的蓝牙协议进行拆分组装返回,供业务使用。
3、$command_parsehex // 16进制转化字符
/**
* 16进制转换成字符串
* @param {Hex} hex
* @returns String
*/
function $command_parsehex(hex) {
var str = '';
for (var i = 0; i < hex.length; i += 2) {
var v = parseInt(hex.substr(i, 2), 16);
// 静态 String.fromCharCode() 方法返回由指定的 UTF-16 代码单元序列创建的字符串。
if (v) str += String.fromCharCode(v);
}
return str;
}
4、$command_splithex // 16进制数据使用业务处理
function $command_splithex(hex, paramsArr) {
let resObj = {};
paramsArr.map((item)=> resObj[item.key] = hex.substr(item.start, item.len))
return resObj;
}
使用示例:
const splithex_chargeArr = [ // 充电 状态/电压/电流/功率
{key: 'status', start:0, len: 4},
{key: 'voltage', start:4, len: 4},
{key: 'electricalCurrent', start:8, len: 4},
{key: 'power', start:12, len: 4},
]
const { status, voltage, electricalCurrent, power } = this.$command_splithex( 16禁止数据, splithex_chargeArr);
5、$command_hexTobuffer // 将16进制转成buffer
function $command_hexTobuffer(HexStr) {
const buffer = new ArrayBuffer(HexStr.length/2);
const dataView = new DataView(buffer);
for (let i = 0; i < HexStr.length; i+=2) {
const idx = i / 2;
dataView.setUint8(idx, '0x' + HexStr.substr(i, 2))
}
return buffer;
}
6、watchNotify // 处理半包数据,返回符合规范的16进制数据
let cache = '';
function checkComplete(str) {
const length = parseInt(str.substring(2, 4), 16);
return str.startsWith('55') && str.endsWith('16') && str.length === length*2 + 8;
}
export function watchNotify(str) {
let complete = checkComplete(str);
if (complete && !cache) {
return str;
}
if((!cache && str.startsWith('55')) || cache.startsWith('55')){
cache += str;
}
console.log(cache, "cache=============>")
complete = checkComplete(cache);
if (complete) {
const res = cache;
cache = '';
return res;
}
}
export function setCache(val){
cache = val
}
使用示例:
// 获取蓝牙传输过来的数据
async getInfoFromBluetooth(res){
const {bindResp, startChargeResp, endChargeResp, chargeStatusResp, orderInfoResp} = this.fieldsMap.operateByCommandCode
const {receiving} = this.fieldsMap.gunStatus
let resDataHexOld = this.$ab2hex(res.value); // 十六进制
// 此处处理 半包容错处理
const hexStr = watchNotify(resDataHexOld);
if(!hexStr){
return uni.showLoading({ title: '等待蓝牙数据返回中...', mask: true});
}
const buffer = this.$command_hexTobuffer(hexStr);
const resHex = hexStr;
uni.hideLoading();
...
}
7、Blob
// 导出文件
exportProlist(){
//得到搜索条件
let searchParams = Object.assign({}, this.filters);
if(searchParams.timeRange && searchParams.timeRange.length > 0){
searchParams.validityEndTimeStart = new Date(searchParams.timeRange[0] + ' 00:00:00').getTime();
searchParams.validityEndTimeEnd = new Date(searchParams.timeRange[1] + ' 23:59:59.999').getTime();
}
delete searchParams.timeRange;
let exportProDate = this._yMdHms(new Date())
searchParams.fileName = `产品开通记录_${exportProDate}`
let searchParamsStr = qs.stringify(searchParams);
// exportExcel(`${netConfig.baseUrl}/export/list?${searchParamsStr}`, searchParams.fileName, this)
zhAxios.post('/export/list', searchParams, { responseType: 'blob' }).then(res => {
let reader = new FileReader()
reader.readAsDataURL(res)
let downloadTime = new Date().format('yyyy-MM-dd hh:mm:ss')
downloadTime = downloadTime.replace(/:/g, ":")
let fileName = `产品开通记录-${downloadTime}.xlsx`
reader.onload = (e) => {
let a = document.createElement('a')
a.href = e.target.result
a.download = fileName
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
}
})
}
引用文章: