RangeError: Array buffer allocation failed

在使用 Node.js 合并文件分片时出现了标题的错误信息。

代码分析

m.merge = (opts) => {
  return new Promise((resolve, reject) => {
    const { filename, target } = opts;
    try {
      let len = 0;
      const bufferList = fs.readdirSync(`${STATIC_TEMPORARY}/${filename}`).map((hash, index) => {
        const buffer = fs.readFileSync(`${STATIC_TEMPORARY}/${filename}/${index}`);
        len += buffer.length;
        return buffer;
      });
      // Merge files
      const buffer = Buffer.concat(bufferList, len);
      const ws = fs.createWriteStream(`${target}/${filename}`);
      ws.write(buffer);
      ws.close();
      resolve({ success: true, msg: 'Section merge completed' });
    } catch (error) {
      console.error(error);
      reject({ success: false, msg: error });
    }
  });
};

const buffer = Buffer.concat(bufferList, len);
定位到是上边这行出现了问题,检查了一下服务器,node应用的内存占用是 192.1mb。

这时原因就很明了,是因为文件分片太大导致内存耗尽,没有可用空间了。

优化方法

上面的做法是把所有的文件分片都concat然后写到流中,正是在这个过程中导致了内存耗尽。
其实,我们可以按顺序分多次concat写入,修改代码如下:

m.merge = (opts) => {
  return new Promise((resolve, reject) => {
    const { filename, target } = opts;

    try {
      // 优化
      const ws = fs.createWriteStream(`${target}/${filename}`);
      const bufferList = fs.readdirSync(`${STATIC_TEMPORARY}/${filename}`)
      let len = 0;
      let list = []
      bufferList.forEach((hash, index) => {
        const b = fs.readFileSync(`${STATIC_TEMPORARY}/${filename}/${index}`);
        len += b.length;
        list.push(b)
        if (len > 10485760) { // 10M
          const buffer = Buffer.concat(list, len);
          ws.write(buffer);
          len = 0;
          list = []
        }
      })
      ws.close();
      resolve({ success: true, msg: 'Section merge completed' });
    } catch (error) {
      console.error(error);
      reject({ success: false, msg: error });
    }
  });
};

这是重新调用接口就不会出现标题的错误了,文件可以合并成功。但是查看一下Node进程的内存占用 却仍然保持在192.1mb左右。

如果,再分析一下,上边的代码还是有些啰嗦,尝试下面的写法:

m.merge = (opts) => {
  return new Promise((resolve, reject) => {
    const { filename, target } = opts;
    try {
      // 优化
      const ws = fs.createWriteStream(`${target}/${filename}`);
      const bufferList = fs.readdirSync(`${STATIC_TEMPORARY}/${filename}`)
      bufferList.forEach((hash, index) => {
        const b = fs.readFileSync(`${STATIC_TEMPORARY}/${filename}/${index}`);
        ws.write(b);
      })
      ws.close();
      resolve({ success: true, msg: 'Section merge completed' });
    } catch (error) {
      console.error(error);
      reject({ success: false, msg: error });
    }
  });
};

直接把读取到的文件写入ws,因为文件大小已经被分片为1M大小,每次读取完写入到ws后就会被Node.js 的GC回收,理论上是比较安全的操作。

修改完毕,经过测试发现,只有在文件上传和合并分片的时候有200M左右的内存占用和较高的CPU消耗,合并完成后就趋于平稳。

在这里插入图片描述

总结

通过一个 RangeError: Array buffer allocation failed 的错误,认识到了内存合理使用的重要性。解决了这个问题,我们的大文件分片上传功能运行更加稳定。

如果你也遇到了这样的问题,希望本文可以帮助到你,还请帮忙点个赞❤️❤️,谢谢。如果你有更高明的方法,欢迎到评论区讨论。

文章首发于 IICOOM-个人博客|技术博客 《RangeError: Array buffer allocation failed》

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JSZip在解压大文件时,可能会出现"Uncaught (in promise) RangeError: Array buffer allocation failed"这样的错误,这是因为JSZip在解压文件时会将文件内容读取到内存中,如果文件太大,内存空间就会不足,导致内存溢出。 为了解决这个问题,可以使用JSZip的chunk方法,将文件内容分成多个小块进行解压,从而避免一次性读取大文件导致内存溢出的问题。 以下是一个使用chunk方法解压文件的示例: ``` JSZipUtils.getBinaryContent(url, function(err, data) { if (err) { throw err; // 处理错误 } JSZip.loadAsync(data).then(function(zip) { zip.forEach(function(relativePath, zipEntry) { if (!zipEntry.dir) { zipEntry.async("arraybuffer").then(function(content) { // 使用chunk方法解压文件内容 var CHUNK_SIZE = 1024 * 1024; // 每个分块大小为1MB var offset = 0; var totalSize = content.byteLength; var chunks = []; while (offset < totalSize) { var chunkSize = Math.min(totalSize - offset, CHUNK_SIZE); var chunk = new Uint8Array(content, offset, chunkSize); chunks.push(chunk); offset += chunkSize; } JSZip.loadAsync(chunks).then(function(unzippedZip) { // 处理解压后的文件 }).catch(function(error) { console.error("解压失败:", error); }); }).catch(function(error) { console.error("读取文件失败:", error); }); } }); }).catch(function(error) { console.error("加载压缩包失败:", error); }); }); ``` 在以上示例中,我们先将文件内容读取到内存中,然后使用JSZip的forEach方法遍历压缩包中的文件,对于非目录文件,我们先使用async方法读取文件内容,然后使用chunk方法将文件内容分块,并使用loadAsync方法解压分块后的内容。这样处理后,即使文件比较大,也可以避免内存溢出问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值