ArrayBuffer和Blob

本文介绍了JavaScript中的ArrayBuffer和Blob两种二进制数据类型。ArrayBuffer是用于存储二进制数据的缓冲区,不能直接读写,需通过TypedArray或DataView视图进行操作。Blob对象则常用于处理文件数据,由一个或多个ArrayBuffer组成。在项目中,ArrayBuffer和Blob在蓝牙传输、数据解析和文件操作等场景中有广泛应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ArrayBuffer和Blob都是JavaScript中的二进制数据类型,但它们之间有一些区别。ArrayBuffer是一种存储二进制数据的类型,它可以被视为一段连续的内存区域,可以用来存储任何类型的二进制数据。Blob则是一种用于表示二进制数据的不可变对象,它一般用于处理文件数据等场景。因此,Blob对象通常是由一个或多个ArrayBuffer对象组成的数组,每个ArrayBuffer对象都包含文件数据的一部分。 

Javascript 二进制对象

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)
           }
      })
}

引用文章:

JavaScript 标准内置对象 - JavaScript | MDN

Blob - Web API 接口参考 | MDN 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值