Skip to content
On this page

fs

1. fs模块

  • 在Node.js中,使用fs模块来实现所有有关文件及目录的创建、写入及删除操作。
  • 在fs模块中,所有的方法都分为同步和异步两种实现。
  • 具有sync后缀的方法为同步方法,不具有sync后缀的方法为异步方法。

2. 整体读取文件

2.1 异步读取

JavaScript
fs.readFile(path[, options], callback)
1
  • options
    • encoding
    • flag flag 默认 = 'r'

2.2 同步读取

JavaScript
fs.readFileSync(path[, options])
1

3. 写入文件

3.1 异步写入

JavaScript
fs.writeFile(file, data[, options], callback)
1
  • options
    • encoding
    • flag flag 默认 = 'w'
    • mode 读写权限,默认为0666
JavaScript
let fs = require('fs');
fs.writeFile('./1.txt', Date.now() + '\n', {
    flag: 'a'
}, function() {
    console.log('ok');
});
1
2
3
4
5
6

3.2 同步写入

JavaScript
fs.writeFileSync(file, data[, options])
1

3.3 追加文件

fs.appendFile(file, data[, options], callback)

JavaScript
fs.appendFile('./1.txt', Date.now() + '\n', function() {
    console.log('ok');
})
1
2
3

3.4 拷贝文件

JavaScript
function copy(src, target) {
    fs.readFile(src, function(err, data) {
        fs.writeFile(target, data);
    })
}
1
2
3
4
5

4. 从指定位置处开始读取文件

4.1 打开文件

fs.open(filename, flags, [mode], callback);

  • FileDescriptor 是文件描述符
  • FileDescriptor 可以被用来表示文件
  • in -- 标准输入(键盘)的描述符
  • out -- 标准输出(屏幕)的描述符
  • err -- 标准错误输出(屏幕)的描述符
JavaScript
fs.open('./1,txt', 'r', 0600, function(err, fd) {});
1

4.2 读取文件

fs.read(fd, buffer, offset, length, position, callback((err, bytesRead, buffer)))

JavaScript
//珠峰培训
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()); //峰培
    })
})
1
2
3
4
5
6
7
8
9
10
11
12

4.3 写入文件

fs.write(fd, buffer[, offset[, length[, position]]], callback)

JavaScript
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()); //珠峰培训
    })
})
1
2
3
4
5
6
7
8
9
10
11

4.4 同步磁盘缓存

fs.fsync(fd, [callback]);

4.5 关闭文件

fs.close(fd, [callback]);

JavaScript
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('写入完毕!')
            });
        });
    })
});
1
2
3
4
5
6
7
8
9
10
11

4.6 拷贝文件

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

5 目录操作

5.1 创建目录

fs.mkdir(path[, mode], callback)

要求父目录必须存在

5.2 判断一个文件是否有权限访问

fs.access(path[, mode], callback)

JavaScript
> fs.access('/etc/passwd', fs.constants.R_OK | fs.constants.W_OK, (err) => {
    >
    console.log(err ? 'no access!' : 'can read/write'); >
}); >
1
2
3
4

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 移动文件或目录

JavaScript
fs.rename(oldPath, newPath, callback)
1

5.6 删除文件

JavaScript
fs.unlink(path, callback)
1

5.7 截断文件

JavaScript
fs.ftruncate(fd[, len], callback)
1
JavaScript
const fd = fs.openSync('temp.txt', 'r+');
// 截断文件至前4个字节
fs.ftruncate(fd, 4, (err) => {
    console.log(fs.readFileSync('temp.txt', 'utf8'));
});
1
2
3
4
5

5.8 监视文件或目录

JavaScript
fs.watchFile(filename[, options], listener)
1
JavaScript
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('修改');
    }
});
1
2
3
4
5
6
7
8
9
10
11

6 递归创建目录

6.1 同步创建目录

JavaScript
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);
        }

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

6.2 异步创建目录

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

6.3 Async+Await创建目录

JavaScript
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);
        }

    }
}
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

7. 递归删除目录

7.1 同步删除目录(深度优先)

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

7.2 异步删除非空目录(Promise版)

JavaScript
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('删除成功');
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

7.3 异步串行删除目录(深度优先)

JavaScript
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');
})
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

7.4 异步并行删除目录(深度优先)

JavaScript
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');
})
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

7.5 同步删除目录(广度优先)

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

7.6 异步删除目录(广度优先)

JavaScript
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();
        }

    }();
}
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

8. 遍历算法

  • 目录是一个树状结构,在遍历时一般使用深度优先+先序遍历算法
  • 深度优先,意味着到达一个节点后,首先接着遍历子节点而不是邻居节点
  • 先序遍历,意味着首次到达了某节点就算遍历完成,而不是最后一次返回某节点才算数
  • 因此使用这种遍历方式时,下边这棵树的遍历顺序是A > B > D > E > C > F。
JavaScript
A
    /
    \
    B C / \\
    D E F
1
2
3
4
5

8.1 同步深度优先+先序遍历

JavaScript
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);
        }
    });
}
1
2
3
4
5
6
7
8
9
10
11
12

8.2 异步深度优先+先序遍历

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

8.3 同步广度优先+先序遍历

JavaScript
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));
            });
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

8.4 异步广度优先+先序遍历

JavaScript
// 异步广度遍历
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'));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

8.path模块

path是node中专门处理路径的一个核心模块

  • path.join 将多个参数值字符串结合为一个路径字符串
  • path.basename 获取一个路径中的文件名
  • path.extname 获取一个路径中的扩展名
  • path.sep 操作系统提定的文件分隔符
  • path.delimiter 属性值为系统指定的环境变量路径分隔符
  • path.normalize 将非标准的路径字符串转化为标准路径字符串 特点:
    • 可以解析 . 和 ..
    • 多个杠可以转换成一个杠
    • 在windows下反杠会转化成正杠
    • 如结尾以杠结尾的,则保留斜杠
  • resolve
    • 以应用程序根目录为起点
    • 如果参数是普通字符串,则意思是当前目录的下级目录
    • 如果参数是.. 回到上一级目录
    • 如果是/开头表示一个绝对的根路径
JavaScript
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 :
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

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+会自动把已有文件的内容清空。

11.linux权限

文件类型与权限链接占用的节点(i-node)文件所有者文件所有者的用户组文件大小文件的创建时间最近修改时间文件名称
沪ICP备20006251号-1