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