大文件上传前台分片后后台合并的问题

  最近做了一个需求,需要加解密大文件,并返回真实加解密进度,因为大文件,所以必须要使用到分片上传。有时候前台分片之后,后台需要合并。前台分片上传没什么太大的问题,就是根据buffer或者blob去分割文件即可。主要是后台合并会遇到一些问题。后台合并的方法有很多,以Node.js为例,可以使用以下方式:

1、buffer合并

// 合并分片
function mergeChunks (fileName, chunks, callback) {
  console.log('chunks:' + chunks)
  let chunkPaths = chunks.map(function (name) {
    return path.join(process.env.IMAGESDIR, name)
  })
  // 采用Buffer方式合并
  const readStream = function (chunkArray, cb) {
    let buffers = []
    chunkPaths.forEach(function (path) {
      let buffer = fs.readFileSync(path)
      buffers.push(buffer)
    })
    let concatBuffer = Buffer.concat(buffers)
    let concatFilePath = path.join(process.env.IMAGESDIR, fileName)
    fs.writeFileSync(concatFilePath, concatBuffer)

    chunkPaths.forEach(function (path) {
      fs.unlinkSync(path)
    })
    cb()
  }
  readStream(chunkPaths, callback)
}

  buffer方式合并是一种常见的文件合并方式,方法是将各个分片文件分别用fs.readFile()方式读取,然后通过Buffer.concat()进行合并。

  这种方法简单易理解,但有个最大的缺点,就是你读取的文件有多大,合并的过程占用的内存就有多大,因为我们相当于把这个大文件的全部内容都一次性载入到内存中了,这是非常低效的。同时,Node默认的缓冲区大小的上限是2GB,一旦我们上传的大文件超出2GB,那使用这种方法就会失败。虽然可以通过修改缓冲区大小上限的方法来规避这个问题,但是鉴于这种合并方式极吃内存,我不建议您这么做。

  那么,有更好的方式吗?那是当然,下面介绍一种stream合并方式。

2、sream流合并

  显然,stream(流)就是这种更好的方式。
// 合并分片
function mergeChunks (fileName, chunks, callback) {
  console.log('chunks:' + chunks)
  let chunkPaths = chunks.map(function (name) {
    return path.join(process.env.IMAGESDIR, name)
  })
  // 采用Stream方式合并
  let targetStream = fs.createWriteStream(path.join(process.env.IMAGESDIR, fileName))
  const readStream = function (chunkArray, cb) {
    let path = chunkArray.shift()
    let originStream = fs.createReadStream(path)
    originStream.pipe(targetStream, {end: false})
    originStream.on('end', function () {
      // 删除文件
      fs.unlinkSync(path)
      if (chunkArray.length > 0) {
        readStream(chunkArray, callback)
      } else {
        cb()
      }
    })
  }
  readStream(chunkPaths, callback)
}

  为什么说流更好呢?流到底是什么呢?

  流是数据的集合 —— 就像数组或字符串一样。区别在于流中的数据可能不会立刻就全部可用,并且你无需一次性的把这些数据全部放入内存。这使得流在操作大量数据或是数据从外部来源逐段发送过来的时候变得非常有用。

  换句话说,当你使用buffer方式来处理一个2GB的文件,占用的内存可能是2GB以上,而当你使用流来处理这个文件,可能只会占用几十个M。这就是我们为什么选择流的原因所在。

  在Node.js中,有4种基本类型的流,分别是可读流,可写流,双向流以及变换流。

  • 可读流是对一个可以读取数据的源的抽象。fs.createReadStream 方法是一个可读流的例子。
  • 可写流是对一个可以写入数据的目标的抽象。fs.createWriteStream 方法是一个可写流的例子。
  • 双向流既是可读的,又是可写的。TCP socket 就属于这种。
  • 变换流是一种特殊的双向流,它会基于写入的数据生成可供读取的数据。

  所有的流都是EventEmitter的实例。它们发出可用于读取或写入数据的事件。然而,我们可以利用pipe方法以一种更简单的方式使用流中的数据。

  在上面那段代码中,我们首先通过fs.createWriteStream()创建了一个可写流,用来存放最终合并的文件。然后使用fs.createReadStream()分别读取各个分片后的文件,再通过pipe()方式将读取的数据像倒水一样“倒”到可写流中,到监控到一杯水倒完后,马上接着倒下一杯,直到全部倒完为止。此时,全部文件合并完毕。

3、追加文件方式合并

  追加文件方式合并指的是使用fs.appendFile()的方式来进行合并。

  fs.appendFile()的作用是异步地追加数据到一个文件,如果文件不存在则创建文件。data可以是一个字符串或buffer。

  使用这种方法也可以将文件合并,性能强过buffer合并方式,但不及流合并方式。

  三种方式各有各的特点,但是在大文件合并上,我推荐使用流方式合并,流合并占内存最少,效率最高,是处理大文件的最佳选择。

原文地址:https://www.cnblogs.com/goloving/p/12825973.html