学习笔记—Node之文件拷贝与分片读写

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


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

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

1
2
3
4
5
6
7
8
9
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 的文件。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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");
});
});
});
});

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
*
* @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) 来进行读写操作。

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

请打赏并支持一下作者吧~

欢迎关注我的微信公众号