日常的学习笔记,包括 ES6、Promise、Node.js、Webpack、http 原理、Vue全家桶,后续可能还会继续更新 Typescript、Vue3 和 常见的面试题 等等。


在之前的文章中,我们曾讲过 fs模块 ,其中包含 readFilewriteFile 两个方法。其中 readFile 是读取文件, writeFile 是写入文件。

下面我们来用这两个方法来实现一个简单的 拷贝操作

const fs = require('fs');
const path = require('path');

fs.readFile(path.resolve(__dirname, './package.json'), (err, data) => {
	if (err) return console.log('error', err);
	fs.writeFile(path.resolve(__dirname, './test.json'), data, () => {
		console.log('success');
	})
})

这样我们的文件就被拷贝下来了,但是这种操作有一个问题。

readFile 只适合操作小文件 (如 JS、Css 和 json 文件等)。但是对于一些大文件 (如 音频、视频 等) ,虽然也可以操作,但是可能会 淹没可用内存。 原因是因为 readFile 是将文件全部读取下来之后,再进行操作的。

这样我们就需要一种新的概念 分片读写,用来操作大型文件。 而分片读写 也就是后端领域中,经常提及的 数据流 (Stream) 操作。

大文件的分片读写

参考文献 fs 文件系统 | Node.js API文档

如果要使用 node 实现一套文件读写,我们需要用到 fs.openfs.readfs.writefs.close

其根本的实现思路就是,将需要进行读写的文件进行 边读边写 的操作,这样我们就可以控制读写的 速率

现在我们先创建一个目标文件 test.js(需要进行拷贝操作的文件),里面随便写一些内容,比如 1234567890

现在我们就需要进行 读一写一(读一个数字写一个数字) 的操作,然后再产生一个名为 newTest.js 的文件。

我们先来实现一套 单个文字 的操作流程,来了解一下读写操作的原理。

let buf = Buffer.alloc(1); // 创建一个用来进行存储数据内存的 Buffer 类型
// 读取 源文件 中的数据
fs.open(path.resolve(__dirname, "test.js"), "r", function (err, rfd) { // fs 是 数字类型
  // 将读取到的数据写入到 buf 中。从第0个位置开始写入buf,写入长度为1个,然后从文件的第0个位置开始进行读取。
  fs.read(rfd, buf, 0, 1, 0, function (err, bytesRead) {
    console.log(bytesRead); // 读取到的字节长度
    // 读取到的第一文字以16进制的形式存入了 buf实例 中。
    console.log(buf); // <Buffer 31>
    // 打开 目标文件。
    fs.open(path.resolve(__dirname, "newTest.js"), "w", function (err, wfd) {
      // 数据已经被写入了
      console.log(rfd, wfd); // 3 4
      // 向文件中写入buf中的数据。从第0个位置开始进行读取,读取长度为1个,然后再写入到文件中。
      fs.write(wfd, buf, 0, 1, 0, function (err, bytesWritten) {
        console.log("success");
      });
    });
  });
});

现在我们可以根据这种方式,然后使用 发布订阅模式,封装一套 分片读写操作。

/**
 *
 * @param {String} source 源文件
 * @param {String} target 目标文件
 * @param {Function} cb 回调函数
 */
function copy(source, target, cb) {
  const BUFFER_SIZE = 3; // buffer的固定长度
  const PATH_SOURCE = path.resolve(__dirname, source);
  const PATH_TARGET = path.resolve(__dirname, target);
  let buf = Buffer.alloc(BUFFER_SIZE); // buffer实例
  let rOffset = 0; // 读取偏移量
  let wOffset = 0; // 写入偏移量
  fs.open(PATH_SOURCE, "r", function (err, rfd) {
    if (err) return cb(err);
    fs.open(PATH_TARGET, "w", function (err, wfd) {
      if (err) return cb(err);
      function next() {
        fs.read(rfd, buf, 0, BUFFER_SIZE, rOffset, function (err, bytesRead) {
          if (err) return cb(err);
          // 如果全部读取完毕,则关闭当前 读写操作
          if (bytesRead == 0) {
            let index = 0;
            let done = () => {
              if (++index == 2) { cb(); }
            };
            fs.close(wfd, done);
            fs.close(rfd, done);
            return;
          }
          fs.write(wfd, buf, 0, bytesRead, wOffset, function (err, bytesWritten) {
              if (err) return cb(err);
              // 读取成功,并更新偏移量
              rOffset += bytesRead;
              wOffset += bytesWritten;
              next();
            }
          );
        });
      }
      next();
    });
  });
};
// 执行封装好的方法
copy("./test.js", "./newTest.js", function (err) {
  if (err) return console.log(err);
  console.log("copy success");
});

这样我们就完成了一套 简单的 分片读写操作。

但是这种方式会出现 回调地狱 的问题,代码看起来非常难以阅读和维护。

这时我们就需要通过 数据流 (Stream) 来进行读写操作。

本篇文章由 莫小尚 创作,文章中如有任何问题和纰漏,欢迎您的指正与交流。
您也可以关注我的 个人站点博客园掘金,我会在文章产出后同步上传到这些平台上。
最后感谢您的支持!

posted on 2022-01-12 03:35  莫小尚  阅读(270)  评论(0编辑  收藏  举报