HarmonyOS鸿蒙Next中使用zlib解压buffer,如何动态设置目标缓冲区的数据长度

HarmonyOS鸿蒙Next中使用zlib解压buffer,如何动态设置目标缓冲区的数据长度

【问题现象】

使用zlib进行解压,在数据解压完成前无法提前的目标缓冲区需要的空间大小,所以目标缓冲区需要提前预设值,而目标缓冲区完成设置后,如果初始化长度不够,将会导致代码抛出异常。目标缓冲区的空间是否可以动态增加,边解压边增加目标缓冲区的空间呢?

【背景知识】

  • zlib内部流解压指导
  • zlib压缩ArrayBuffer指导
  • zlib解压ArrayBuffer指导

【定位思路】

  1. 压缩的ArrayBuff转成内部流的方式进行解压。
  2. 将解压的流,循环每次只转换为一个固定大小的ArrayBuffer,并将此ArrayBuffer作为元素添加到一个输出的数组对象,下次循环 以前一次结束位置作为新起点的平移量重新执行下一次转换。
  3. ArrayBuffer数组对象元素按顺序循环合并为一个最终输出的Uint8Array对象。
  4. 参考示意图如下:

点击放大

【解决方案】

1. 核心解压逻辑:

  • inBuf:ArrayBuffer转strm:zlib.ZStream,每次的大小为readLen。
  • 循环->BUFLEN计算stream的偏移量,作为起点继续下一个截取的stream转换 [循环->(用BUFLEN大小的outBuf:ArrayBuffer缓冲目标区去接收分割后的stream]。
  • 判断当前的stream是否为最后的一个,如果是结束循环。
/**
  * (1) inBuf:ArrayBuffer转 strm:zlib.ZStream 每次的大小为 let readLen = Math.min(BUFLEN, inBuf.byteLength - offset)
  * 循环-> BUFLEN计算stream的偏移量,作为起点继续下一个截取的stream转换  [循环->(用BUFLEN大小的outBuf:ArrayBuffer缓冲目标区去接收分割后的stream]
  * (3) 计算stream的偏移量,作为起点继续下一个截取的stream转换
  * (4) 判断当前的stream是否为最后的一个,如果是结束循环
  */
 async function unzip(inBuf: ArrayBuffer) {

   let zip = zlib.createZipSync();
   let gzip = zlib.createGZipSync()

   // 将压缩后的arrayBuffer 解压的正确姿势
   let strm: zlib.ZStream = {};
   let BUFLEN = 4096;
   let count = inBuf.byteLength / BUFLEN
   if (count > 1) {
     BUFLEN *= count
   }

   let outBuf = new ArrayBuffer(BUFLEN);
   let output: ArrayBuffer[] = []; // 用于存储解压后的数据

   let initStatus = zip.inflateInit(strm);
   console.debug('inflateInit ret: ' + (await initStatus).valueOf());

   let offset = 0; // 用于跟踪读取的字节数
   do {
     let readLen = Math.min(BUFLEN, inBuf.byteLength - offset);
     if (readLen <= 0) {
       break;
     }

     strm.availableIn = readLen;
     strm.nextIn = inBuf.slice(offset, offset + readLen);
     offset += readLen; // 更新偏移量

     do {
       strm.availableOut = BUFLEN;
       strm.nextOut = outBuf; // 压缩后的输出字节

       try {
         let inflateStatus = zip.inflate(strm, zlib.CompressFlushMode.SYNC_FLUSH);
         console.debug('inflate ret: ' + (await inflateStatus).valueOf());

         let innerStrm = zip.getZStream();
         strm.availableIn = (await innerStrm).availableIn;
         strm.nextIn = (await innerStrm).nextIn;
         strm.availableOut = (await innerStrm).availableOut;
         strm.nextOut = (await innerStrm).nextOut;
         strm.totalIn = (await innerStrm).totalIn;
         strm.totalOut = (await innerStrm).totalOut;

         if (strm.availableOut != undefined) {
           let have = BUFLEN - strm.availableOut;
           if (have > 0) {
             // 将解压后的数据存储到 output 数组中
             output.push(outBuf.slice(0, have));
           }
         }
       } catch (err) {
         console.debug('inflate err: ' + JSON.stringify(err));
       }
     } while (strm.availableOut == 0);
   } while (strm.availableIn! > 0 || strm.availableOut! > 0);

   zip.inflateEnd(strm);

   // 将 output 数组中的 Uint8Array 合并为一个单一的 Uint8Array
   let totalLength = output.reduce((acc, val) => acc + val.byteLength, 0);
   // 解压后的buff
   let result = new Uint8Array(totalLength);
   let pos = 0;
   for (let arr of output) {
     // 将 ArrayBuffer 转换为 Uint8Array
     let u = new Uint8Array(arr);
     // 将解压后的数据写入 result
     result.set(u, pos);
     // 更新位置
     pos += u.byteLength;
   }

   // 转回string检测结果正确
   let strData: string = uint8ArrayToString(result)
   console.log(strData)
 }

2. 压缩对象并输出ArrayBuffer核心代码参考:

function zip(str: string): ArrayBuffer {
   let arrayBufferIn = new ArrayBuffer(str.length);
   let byteArray = new Uint8Array(arrayBufferIn);

   for (let i = 0, j = str.length; i < j; i++) {
     byteArray[i] = str.charCodeAt(i)
   }
   // arrayBufferOut长度必须足够,否则会抛出异常
   let arrayBufferOut = new ArrayBuffer(100);
   let zip = zlib.createZipSync();

   zip.compress2(arrayBufferOut, arrayBufferIn, zlib.CompressLevel.COMPRESS_LEVEL_BEST_SPEED).then((data) => {
     console.info('compress2 success');
   }).catch((errData: BusinessError) => {
     console.error(`errData is errCode:${errData.code}  message:${errData.message}`);
   })
   return arrayBufferOut
 }

3. string互转Uint8Array核心代码参考:

/**
  * string转Uint8Array
  * 
  */
 function stringToUint8Array(str: string): Uint8Array {
   try {
     let textEncoder = new util.TextEncoder("utf-8");
     let array: Uint8Array = textEncoder.encodeInto(str);
     return array
   } catch (err) {
     console.log(err)
     return new Uint8Array()
   }
 }

/**
  * Uint8Array转string
  * 
  */
 function uint8ArrayToString(arr: Uint8Array): string {
   let str = ''
   if (arr && arr.length > 0) {
     try {
       let textDecode = util.TextDecoder.create('utf-8')
       str = textDecode.decodeWithStream(arr)
     } catch (err) {

     }
   }
   return str
 }

更多关于HarmonyOS鸿蒙Next中使用zlib解压buffer,如何动态设置目标缓冲区的数据长度的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS鸿蒙Next中使用zlib解压buffer,如何动态设置目标缓冲区的数据长度的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中使用zlib解压buffer时,动态设置目标缓冲区的数据长度可以通过以下步骤实现:

  1. 初始化zlib流:使用inflateInit2函数初始化zlib流,设置解压缩参数。

  2. 设置输入缓冲区:将需要解压的数据放入输入缓冲区,并设置z_stream结构体中的next_inavail_in字段。

  3. 动态分配输出缓冲区:根据解压缩数据的预期大小,动态分配输出缓冲区。可以使用malloccalloc函数进行内存分配。

  4. 设置输出缓冲区:将分配的输出缓冲区地址赋值给z_stream结构体中的next_out字段,并设置avail_out字段为缓冲区的大小。

  5. 解压缩数据:调用inflate函数进行解压缩。如果avail_out为0,表示输出缓冲区已满,需要重新分配更大的缓冲区,并继续解压缩。

  6. 处理解压缩结果:解压缩完成后,检查inflate函数的返回值,确保解压缩成功。如果解压缩成功,输出缓冲区中即为解压后的数据。

  7. 释放资源:使用inflateEnd函数释放zlib流,并释放动态分配的缓冲区。

示例代码片段如下:

#include <zlib.h>
#include <stdlib.h>

void decompressBuffer(const Bytef *src, uLong srcLen, Bytef **dest, uLong *destLen) {
    z_stream strm;
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = srcLen;
    strm.next_in = (Bytef *)src;

    inflateInit2(&strm, 15 + 32);

    *destLen = 4096; // 初始缓冲区大小
    *dest = (Bytef *)malloc(*destLen);
    strm.avail_out = *destLen;
    strm.next_out = *dest;

    int ret;
    do {
        ret = inflate(&strm, Z_NO_FLUSH);
        if (ret == Z_OK && strm.avail_out == 0) {
            *destLen *= 2;
            *dest = (Bytef *)realloc(*dest, *destLen);
            strm.next_out = *dest + (*destLen / 2);
            strm.avail_out = *destLen / 2;
        }
    } while (ret == Z_OK);

    inflateEnd(&strm);
}

此代码展示了如何动态设置目标缓冲区的数据长度,并在解压缩过程中根据需要调整缓冲区大小。

回到顶部