·vincent
Node fs模块
node,fs
nodefs
1. fs 模块
- 在 Node.js 中,使用 fs 模块来实现所有有关文件及目录的创建、写入及删除操作。
- 在 fs 模块中,所有的方法都分为同步和异步两种实现。
- 具有
sync
后缀的方法为同步方法,不具有sync
后缀的方法为异步方法。
2. 整体读取文件
2.1 异步读取
js
1fs.readFile(path[, options], callback)
- options
- encoding
- flag flag 默认 = 'r'
2.2 同步读取
js
1fs.readFileSync(path[, options])
3. 写入文件
3.1 异步写入
js
1fs.writeFile(file, data[, options], callback)
- options
- encoding
- flag flag 默认 = 'w'
- mode 读写权限,默认为 0666
js
1let fs = require("fs");
2fs.writeFile("./1.txt", Date.now() + "\n", { flag: "a" }, function () {
3 console.log("ok");
4});
3.2 同步写入
js
1fs.writeFileSync(file, data[, options])
3.3 追加文件
fs.appendFile(file, data[, options], callback)
js
1fs.appendFile("./1.txt", Date.now() + "\n", function () {
2 console.log("ok");
3});
3.4 拷贝文件
js
1function copy(src, target) {
2 fs.readFile(src, function (err, data) {
3 fs.writeFile(target, data);
4 });
5}
4. 从指定位置处开始读取文件
4.1 打开文件
fs.open(filename,flags,[mode],callback);
- FileDescriptor 是文件描述符
- FileDescriptor 可以被用来表示文件
- in -- 标准输入(键盘)的描述符
- out -- 标准输出(屏幕)的描述符
- err -- 标准错误输出(屏幕)的描述符
js
1fs.open("./1,txt", "r", 0600, function (err, fd) {});
4.2 读取文件
fs.read(fd, buffer, offset, length, position, callback((err, bytesRead, buffer)))
js
1// 前端笔记
2const fs = require("fs");
3const path = require("path");
4fs.open(path.join(__dirname, "1.txt"), "r", 0o666, function (err, fd) {
5 console.log(err);
6 let buf = Buffer.alloc(6);
7 fs.read(fd, buf, 0, 6, 3, function (err, bytesRead, buffer) {
8 console.log(bytesRead); //6
9 console.log(buffer === buf); //true
10 console.log(buf.toString());
11 });
12});
4.3 写入文件
fs.write(fd, buffer[, offset[, length[, position]]], callback)
js
1const fs = require("fs");
2const path = require("path");
3fs.open(path.join(__dirname, "1.txt"), "w", 0o666, function (err, fd) {
4 console.log(err);
5 let buf = Buffer.from(" 前端笔记");
6 fs.write(fd, buf, 3, 6, 0, function (err, bytesWritten, buffer) {
7 console.log(bytesWritten); //6
8 console.log(buffer === buf); //true
9 console.log(buf.toString()); // 前端笔记
10 });
11});
4.4 同步磁盘缓存
fs.fsync(fd,[callback]);
4.5 关闭文件
fs.close(fd,[callback]);
js
1let buf = Buffer.from("前端笔记");
2fs.open("./2.txt", "w", function (err, fd) {
3 fs.write(fd, buf, 3, 6, 0, function (err, written, buffer) {
4 console.log(written);
5 fs.fsync(fd, function (err) {
6 fs.close(fd, function (err) {
7 console.log("写入完毕!");
8 });
9 });
10 });
11});
4.6 拷贝文件
js
1let BUFFER_SIZE = 1;
2const path = require("path");
3const fs = require("fs");
4function copy(src, dest, callback) {
5 let buf = Buffer.alloc(BUFFER_SIZE);
6 fs.open(src, "r", (err, readFd) => {
7 fs.open(dest, "w", (err, writeFd) => {
8 !(function read() {
9 fs.read(readFd, buf, 0, BUFFER_SIZE, null, (err, bytesRead) => {
10 bytesRead && fs.write(writeFd, buf, 0, bytesRead, read);
11 });
12 })();
13 });
14 });
15}
16copy(path.join(__dirname, "1.txt"), path.join(__dirname, "2.txt"), () =>
17 console.log("ok")
18);
5 目录操作
5.1 创建目录
fs.mkdir(path[, mode], callback)
要求父目录必须存在
5.2 判断一个文件是否有权限访问
fs.access(path[, mode], callback)
js1fs.access("/etc/passwd", fs.constants.R_OK | fs.constants.W_OK, (err) => { 2 console.log(err ? "no access!" : "can read/write"); 3});
5.3 读取目录下所有的文件
fs.readdir(path[, options], callback)
5.4 查看文件目录信息
fs.stat(path, callback)
- stats.isFile()
- stats.isDirectory()
- atime(Access Time)上次被读取的时间。
- ctime(State Change Time):属性或内容上次被修改的时间。
- mtime(Modified time):档案的内容上次被修改的时间。
5.5 移动文件或目录
js
1fs.rename(oldPath, newPath, callback);
5.6 删除文件
js
1fs.unlink(path, callback);
5.7 截断文件
js
1fs.ftruncate(fd[, len], callback)
js
1const fd = fs.openSync("temp.txt", "r+");
2// 截断文件至前4个字节
3fs.ftruncate(fd, 4, (err) => {
4 console.log(fs.readFileSync("temp.txt", "utf8"));
5});
5.8 监视文件或目录
js
1fs.watchFile(filename[, options], listener)
js
1let fs = require("fs");
2fs.watchFile("1.txt", (curr, prev) => {
3 //parse() 方法可解析一个日期时间字符串,并返回 1970/1/1 午夜距离该日期时间的毫秒数。
4 if (Date.parse(prev.ctime) == 0) {
5 console.log("创建");
6 } else if (Date.parse(curr.ctime) == 0) {
7 console.log("删除");
8 } else if (Date.parse(prev.ctime) != Date.parse(curr.ctime)) {
9 console.log("修改");
10 }
11});
6 递归创建目录
6.1 同步创建目录
js
1let fs = require("fs");
2let path = require("path");
3function makepSync(dir) {
4 let parts = dir.split(path.sep);
5 for (let i = 1; i <= parts.length; i++) {
6 let parent = parts.slice(0, i).join(path.sep);
7 try {
8 fs.accessSync(parent);
9 } catch (error) {
10 fs.mkdirSync(parent);
11 }
12 }
13}
6.2 异步创建目录
js
1function makepAsync(dir, callback) {
2 let parts = dir.split(path.sep);
3 let i = 1;
4 function next() {
5 if (i > parts.length) return callback && callback();
6 let parent = parts.slice(0, i++).join(path.sep);
7 fs.access(parent, (err) => {
8 if (err) {
9 fs.mkdir(parent, next);
10 } else {
11 next();
12 }
13 });
14 }
15 next();
16}
6.3 Async+Await 创建目录
js
1async function mkdir(parent) {
2 return new Promise((resolve, reject) => {
3 fs.mkdir(parent, (err) => {
4 if (err) reject(err);
5 else resolve();
6 });
7 });
8}
9
10async function access(parent) {
11 return new Promise((resolve, reject) => {
12 fs.access(parent, (err) => {
13 if (err) reject(err);
14 else resolve();
15 });
16 });
17}
18async function makepPromise(dir, callback) {
19 let parts = dir.split(path.sep);
20 for (let i = 1; i <= parts.length; i++) {
21 let parent = parts.slice(0, i).join(path.sep);
22 try {
23 await access(parent);
24 } catch (err) {
25 await mkdir(parent);
26 }
27 }
28}
7. 递归删除目录
7.1 同步删除目录(深度优先) >)
js
1let fs = require("fs");
2let path = require("path");
3function rmSync(dir) {
4 try {
5 let stat = fs.statSync(dir);
6 if (stat.isFile()) {
7 fs.unlinkSync(dir);
8 } else {
9 let files = fs.readdirSync(dir);
10 files.map((file) => path.join(dir, file)).forEach((item) => rmSync(item));
11 fs.rmdirSync(dir);
12 }
13 } catch (e) {
14 console.log("删除失败!");
15 }
16}
17rmSync(path.join(__dirname, "a"));
7.2 异步删除非空目录(Promise 版) >)
js
1function rmPromise(dir) {
2 return new Promise((resolve, reject) => {
3 fs.stat(dir, (err, stat) => {
4 if (err) return reject(err);
5 if (stat.isDirectory()) {
6 fs.readdir(dir, (err, files) => {
7 let paths = files.map((file) => path.join(dir, file));
8 let promises = paths.map((p) => rmPromise(p));
9 Promise.all(promises).then(() => fs.rmdir(dir, resolve));
10 });
11 } else {
12 fs.unlink(dir, resolve);
13 }
14 });
15 });
16}
17rmPromise(path.join(__dirname, "a")).then(() => {
18 console.log("删除成功");
19});
7.3 异步串行删除目录(深度优先) >)
js
1function rmAsyncSeries(dir, callback) {
2 setTimeout(() => {
3 fs.stat(dir, (err, stat) => {
4 if (err) return callback(err);
5 if (stat.isDirectory()) {
6 fs.readdir(dir, (err, files) => {
7 let paths = files.map((file) => path.join(dir, file));
8 function next(index) {
9 if (index >= files.length) return fs.rmdir(dir, callback);
10 let current = paths[index];
11 rmAsyncSeries(current, () => next(index + 1));
12 }
13 next(0);
14 });
15 } else {
16 fs.unlink(dir, callback);
17 }
18 });
19 }, 1000);
20}
21
22console.time("cost");
23rmAsyncSeries(path.join(__dirname, "a"), (err) => {
24 console.timeEnd("cost");
25});
7.4 异步并行删除目录(深度优先) >)
js
1function rmAsyncParallel(dir, callback) {
2 setTimeout(() => {
3 fs.stat(dir, (err, stat) => {
4 if (err) return callback(err);
5 if (stat.isDirectory()) {
6 fs.readdir(dir, (err, files) => {
7 let paths = files.map((file) => path.join(dir, file));
8 if (paths.length > 0) {
9 let i = 0;
10 function done() {
11 if (++i == paths.length) {
12 fs.rmdir(dir, callback);
13 }
14 }
15 paths.forEach((p) => rmAsyncParallel(p, done));
16 } else {
17 fs.rmdir(dir, callback);
18 }
19 });
20 } else {
21 fs.unlink(dir, callback);
22 }
23 });
24 }, 1000);
25}
26console.time("cost");
27rmAsyncParallel(path.join(__dirname, "a"), (err) => {
28 console.timeEnd("cost");
29});
7.5 同步删除目录(广度优先) >)
js
1function rmSync(dir) {
2 let arr = [dir];
3 let index = 0;
4 while (arr[index]) {
5 let current = arr[index++];
6 let stat = fs.statSync(current);
7 if (stat.isDirectory()) {
8 let dirs = fs.readdirSync(current);
9 arr = [...arr, ...dirs.map((d) => path.join(current, d))];
10 }
11 }
12 let item;
13 while (null != (item = arr.pop())) {
14 let stat = fs.statSync(item);
15 if (stat.isDirectory()) {
16 fs.rmdirSync(item);
17 } else {
18 fs.unlinkSync(item);
19 }
20 }
21}
7.6 异步删除目录(广度优先) >)
js
1function rmdirWideAsync(dir, callback) {
2 let dirs = [dir];
3 let index = 0;
4 function rmdir() {
5 let current = dirs.pop();
6 if (current) {
7 fs.stat(current, (err, stat) => {
8 if (stat.isDirectory()) {
9 fs.rmdir(current, rmdir);
10 } else {
11 fs.unlink(current, rmdir);
12 }
13 });
14 }
15 }
16 !(function next() {
17 let current = dirs[index++];
18 if (current) {
19 fs.stat(current, (err, stat) => {
20 if (err) callback(err);
21 if (stat.isDirectory()) {
22 fs.readdir(current, (err, files) => {
23 dirs = [...dirs, ...files.map((item) => path.join(current, item))];
24 next();
25 });
26 } else {
27 next();
28 }
29 });
30 } else {
31 rmdir();
32 }
33 })();
34}
8. 遍历算法
-
目录是一个树状结构,在遍历时一般使用深度优先+先序遍历算法
-
深度优先,意味着到达一个节点后,首先接着遍历子节点而不是邻居节点
-
先序遍历,意味着首次到达了某节点就算遍历完成,而不是最后一次返回某节点才算数
-
因此使用这种遍历方式时,下边这棵树的遍历顺序是 A > B > D > E > C > F。
js1 A 2 / \ 3 B C 4 / \ \ 5 D E F
8.1 同步深度优先+先序遍历
js1function deepSync(dir) { 2 console.log(dir); 3 fs.readdirSync(dir).forEach((file) => { 4 let child = path.join(dir, file); 5 let stat = fs.statSync(child); 6 if (stat.isDirectory()) { 7 deepSync(child); 8 } else { 9 console.log(child); 10 } 11 }); 12}
8.2 异步深度优先+先序遍历
js1function deep(dir, callback) { 2 console.log(dir); 3 fs.readdir(dir, (err, files) => { 4 !(function next(index) { 5 if (index == files.length) { 6 return callback(); 7 } 8 let child = path.join(dir, files[index]); 9 fs.stat(child, (err, stat) => { 10 if (stat.isDirectory()) { 11 deep(child, () => next(index + 1)); 12 } else { 13 console.log(child); 14 next(index + 1); 15 } 16 }); 17 })(0); 18 }); 19}
8.3 同步广度优先+先序遍历
js1function wideSync(dir) { 2 let dirs = [dir]; 3 while (dirs.length > 0) { 4 let current = dirs.shift(); 5 console.log(current); 6 let stat = fs.statSync(current); 7 if (stat.isDirectory()) { 8 let files = fs.readdirSync(current); 9 files.forEach((item) => { 10 dirs.push(path.join(current, item)); 11 }); 12 } 13 } 14}
8.4 异步广度优先+先序遍历
js
1// 异步广度遍历
2function wide(dir, cb) {
3 console.log(dir);
4 cb && cb();
5 fs.readdir(dir, (err, files) => {
6 !(function next(i) {
7 if (i >= files.length) return;
8 let child = path.join(dir, files[i]);
9 fs.stat(child, (err, stat) => {
10 if (stat.isDirectory()) {
11 wide(child, () => next(i + 1));
12 } else {
13 console.log(child);
14 next(i + 1);
15 }
16 });
17 })(0);
18 });
19}
20wide(path.join(__dirname, "a"));
8. path 模块
path 是 node 中专门处理路径的一个核心模块
- path.join 将多个参数值字符串结合为一个路径字符串
- path.basename 获取一个路径中的文件名
- path.extname 获取一个路径中的扩展名
- path.sep 操作系统提定的文件分隔符
- path.delimiter 属性值为系统指定的环境变量路径分隔符
- path.normalize 将非标准的路径字符串转化为标准路径字符串 特点:
- 可以解析 . 和 ..
- 多个杠可以转换成一个杠
- 在 windows 下反杠会转化成正杠
- 如结尾以杠结尾的,则保留斜杠
- resolve
- 以应用程序根目录为起点
- 如果参数是普通字符串,则意思是当前目录的下级目录
- 如果参数是.. 回到上一级目录
- 如果是/开头表示一个绝对的根路径
js
1var path = require("path");
2var fs = require("fs");
3/**
4 * normalize 将非标准化的路径转化成标准化的路径
5 * 1.解析. 和 ..
6 * 2.多个斜杠会转成一个斜杠
7 * 3.window下的斜杠会转成正斜杠
8 * 4.如果以斜杠会保留
9 **/
10
11console.log(path.normalize("./a////b//..\\c//e//..//"));
12// \a\c\
13
14//多个参数字符串合并成一个路径 字符串
15console.log(path.join(__dirname, "a", "b"));
16
17/**
18 * resolve
19 * 以就用程序为根目录,做为起点,根据参数解析出一个绝对路径
20 * 1.以应用程序为根起点
21 * 2... .
22 * 3. 普通 字符串代表子目录
23 * 4. /代表绝地路径根目录
24 */
25console.log(path.resolve()); //空代表当前的目录 路径
26console.log(path.resolve("a", "/c")); // /a/b
27// d:\c
28//可以获取两个路径之间的相对关系
29console.log(path.relative(__dirname, "/a"));
30// a
31//返回指定路径的所在目录
32console.log(path.dirname(__filename)); // 9.path
33console.log(path.dirname("./1.path.js")); // 9.path
34//basename 获取路径中的文件名
35console.log(path.basename(__filename));
36console.log(path.basename(__filename, ".js"));
37console.log(path.extname(__filename));
38
39console.log(path.sep); //文件分隔符 window \ linux /
40console.log(path.win32.sep);
41console.log(path.posix.sep);
42console.log(path.delimiter); //路径 分隔符 window ; linux :
9. flags
符号 | 含义 |
---|---|
r | 读文件,文件不存在报错 |
r+ | 读取并写入,文件不存在报错 |
rs | 同步读取文件并忽略缓存 |
w | 写入文件,不存在则创建,存在则清空 |
wx | 排它写入文件 |
w+ | 读取并写入文件,不存在则创建,存在则清空 |
wx+ | 和 w+类似,排他方式打开 |
a | 追加写入 |
ax | 与 a 类似,排他方式写入 |
a+ | 读取并追加写入,不存在则创建 |
ax+ | 作用与 a+类似,但是以排他方式打开文件 |
10. 助记
- r 读取
- w 写入
- s 同步
- + 增加相反操作
- x 排他方式
- r+ w+的区别?
- 当文件不存在时,r+不会创建,而会导致调用失败,但 w+会创建。
- 如果文件存在,r+不会自动清空文件,但 w+会自动把已有文件的内容清空。