日常的学习笔记,包括 ES6、Promise、Node.js、Webpack、http 原理、Vue全家桶,后续可能还会继续更新 Typescript、Vue3 和 常见的面试题 等等。
在之前的文章中,我们曾讲过 fs模块 ,其中包含 readFile
和 writeFile
两个方法。其中 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
) 操作。
大文件的分片读写
如果要使用 node 实现一套文件读写,我们需要用到 fs.open、fs.read、fs.write、fs.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
) 来进行读写操作。
本篇文章由 莫小尚 创作,文章中如有任何问题和纰漏,欢迎您的指正与交流。
您也可以关注我的 个人站点、博客园 和 掘金,我会在文章产出后同步上传到这些平台上。
最后感谢您的支持!