fs文件系统模块是NodeJS核心模块之一,用于对系统文件及目录进行读写操作,官方文档讲解了fs模块主要提供三种API机制以供开发者使用。
基于Promise的方式fs/promises API 提供了返回 promise 的异步的文件系统方法。
注意:Promise API 使用底层的 Node.js 线程池在事件循环线程之外执行文件系统操作。 这些操作不是同步的也不是线程安全的。 对同一文件执行多个并发修改时必须小心,否则可能会损坏数据。
基于callback回调方式回调的 API 异步地执行所有操作,不会阻塞事件循环,然后在完成或错误时调用回调函数。
注意:回调的 API 使用底层 Node.js 线程池在事件循环线程之外执行文件系统操作。 这些操作不是同步的也不是线程安全的。 对同一文件执行多个并发修改时必须小心,否则可能会损坏数据。
基于同步的方式同步的 API 同步地执行所有操作,阻塞事件循环,直到操作完成或失败。
本篇文章以回调的API为例,分析总结fs模块常用文件操作API的用法及注意事项。
1. 权限相关
1. 1 fs.access(path[, mode], callback)
测试用户对 path 指定的文件或目录的权限。
参数:
- path: string | Buffer | url
- mode: fs.constants.F_OK(存在性) | fs.constants.R_OK(可读性) | fs.constants.W_OK(可写性)
- callback: 回调函数,传入值为可能错误的err
import fs from 'fs';
import path from 'path';
const rootDir = path.resolve();
const testPath = path.join(rootDir, 'files', 'abc.text');
fs.access(testPath, fs.constants.R_OK, (err) => {
if (err) {
console.log(err)
} else {
console.log('存在文件')
}
})
注意:path不存在或者没有权限都将调用回调函数并传入err对象。
1. 2 fs.chmod(path, mode, callback)
异步地更改文件的权限。
参数:
- path: string | Buffer | url
- mode: string | integger; mode参数说明参考
- callback: Function(err)
1. 3 fs.fchmod(fd, mode, callback)
设置文件的权限
参数:
- fd: integger
- mode: string | integger; mode参数说明参考
- callback: Function(err)
注意:此方法与fs.chmod()方法唯一不同点在于参数fd,通过文件描述符修改文件权限。其它参数一致。
1. 4 fs.chown(path, uid, gid, callback)
异步地更改文件的所有者和群组
参数:
- path:string | Buffer | url
- uid: integger
- gid: integger
- callback: 回调函数
1. 5 fs.fchown(fd, uid, gid, callback)
设置文件的所有者
参数:
- fd:integger
- uid: integger
- gid: integger
- callback: 回调函数
注意:次方法与fs.chown()方法的区别主要在第一个参数的区别。fd为文件描述符。
2. 新建相关
2.1 fs.mkdir(path[, options], callback)
异步地创建目录。
参数:
- path: string | Buffer | url
- options: integer | Object
- callback: Function(err, path?)
fs.mkdir(path.join(path.resolve(), 'cssFiles'), {recursive: true}, (err,path) => {
if (err) {
return;
}
// path仅在options recursive: true时存在
console.log(path)
})
3. 文件信息
3. 1 fs.stat(path[, options], callback)
获取文件状态信息
参数:
- path: string | Buffer | url
- options: Object
- callback:回调函数
import fs from 'fs';
import path from 'path';
const rootDir = path.resolve();
const testPath = path.join(rootDir, 'files', 'text.txt');
fs.stat(testPath,(err,stats) => {
if (err) {
console.log(err);
return;
}
console.log(stats);
{
dev: 4271079436,
mode: 33206,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
blksize: 4096,
ino: 281474980389124,
size: 1360,
blocks: 8,
atimeMs: 1628750429976.6963,
mtimeMs: 1628750410741.5435,
ctimeMs: 1628750410741.5435,
birthtimeMs: 1628750410738.552,
atime: 2021-08-12T06:40:29.977Z,
mtime: 2021-08-12T06:40:10.742Z,
ctime: 2021-08-12T06:40:10.742Z,
birthtime: 2021-08-12T06:40:10.739Z
}
})
3. 2 fs.fstat(fd[, options], callback)
使用文件描述符获取文件描述信息
注意:次方法与fs.stat()方法区别在于第一个参数fd,文件描述符
4. 打开访问相关
4.1 fs.open(path[, flags[, mode]], callback)
异步地打开文件
import fs from 'fs';
import path from 'path';
const rootDir = path.resolve();
const testPath = path.join(rootDir, 'files', 'text.txt');
fs.open(testPath, (err, fd) => {
if (err) {
console.log(err);
return;
}
console.log(fd); // 3
})
4.2 fs.opendir(path[, options], callback)
异步地打开目录
import fs from 'fs';
import path from 'path';
const rootDir = path.resolve();
const testDir = path.join(rootDir, 'files');
const testPath = path.join(rootDir, 'files', 'text.txt');
fs.opendir(testDir, async(err, files) => {
if (err) {
console.log(err);
return;
}
for await (const f of files) {
console.log(f)
}
})
// 打印结果:
// Dirent { name: 'abc.css', [Symbol(type)]: 1 }
// Dirent { name: 'text.txt', [Symbol(type)]: 1 }
4.3 fs.close(fd[, callback])
关闭文件描述符。
5. 读取相关
5.1 fs.read(fd)
从 fd(文件描述符) 指定的文件中读取数据 此方法有两种使用方式:
- fs.read(fd, buffer, offset, length, position, callback)
- fs.read(fd, [options,] callback)
import fs from 'fs';
import path from 'path';
import {Buffer} from 'buffer';
const rootDir = path.resolve();
const testDir = path.join(rootDir, 'files');
const testPath = path.join(rootDir, 'files', 'text.txt');
fs.stat(testPath, (err1, stat) => {
if (err1) {
return;
}
fs.open(testPath, (err2, fd) => {
if (err2) {
return;
}
const len = stat.size;
const offset = 0;
const filePosition = 0;
// 创建一个与文件大小相同的缓冲区
const readBuffer = Buffer.alloc(len);
// 读取文件全部内容
fs.read(fd, readBuffer, offset, len, filePosition, (err3, bytesRead, buffer) => {
if (err3) {
return;
}
console.log(bytesRead)
console.log(buffer.toString());
})
})
})
5.2 fs.readFile(path[, options], callback)
异步地读取文件的全部内容。
参数:
- path: string | Buffer | url
- options: Object | string
- callback:Function(err, data)
参数说明:
- 如果未指定编码,则返回原始缓冲区。
- 如果
options是字符串,则它指定编码:如utf8,base64
import fs from 'fs';
import path from 'path';
const rootDir = path.resolve();
const testDir = path.join(rootDir, 'files');
const testPath = path.join(rootDir, 'files', 'text.txt');
fs.readFile(testPath, 'utf8', (err, data) => {
if (err) {
return;
}
console.log(data)
})
read()与readFile()对比说明
-
fs.readFile()方法是对fs.read()方法的进一步封装,以方便的读取文件的全部内容。
-
相比fs.readFile()方法,使用fs.read()方法读取文件的全部内容就要复杂的多。首先要用fs.stat或fs.fstat判断文件的大小,然后使用fs.open()创建文件描述符,最后再使用fs.read()方法读取文件内容。
-
fs.read更适合读取部分文件数据内容。
5.3 fs.readdir(path[, options], callback)
异步读取目录的内容
参数:
- path: string | Buffer | url
- options: string | Object
- callback: Function(err, files)
import fs from 'fs';
import path from 'path';
import {Buffer} from 'buffer';
const rootDir = path.resolve();
const testDir = path.join(rootDir, 'files');
const testPath = path.join(rootDir, 'files', 'text.txt');
fs.readdir(testDir, (err, files) => {
if (err) {
return;
}
console.log(files)
})
// 结果
[ 'abc.css', 'text.txt' ]
Dirent { name: 'abc.css', [Symbol(type)]: 1 }
Dirent { name: 'text.txt', [Symbol(type)]: 1 }
6. 写入相关
6.1 fs.write()
将数据写入指定fd
与fs.read()相似,同样有两种使用方式
- fs.write(fd, buffer[, offset[, length[, position]]], callback)
- fs.write(fd, string[, position[, encoding]], callback)
6.2 fs.writeFile(file, data[, options], callback)
fs.writeFile(path.join(path.resolve(), 'files', 'newText.txt'), 'hello fs.wirteFile2', (err)=> {
if (err) {
throw err;
return;
}
console.log('数据写入成功')
})
注意说明
- 如果file不存在则新建file
- 如果file存在则覆盖file原有数据
- writeFile是对write的封装
6.3 fs.appendFile(path, data[, options], callback)
异步地将数据追加到文件
fs.appendFile(path.join(path.resolve(), 'files', 'newText.txt'), '这是一段新追加文本内容', (err) => {
if (err) {
return;
}
console.log('数据追加成功')
})
注意说明:
- 如果file不存在则新建file
- 相比fs.writeFile不同,file存在则追加内容,不进行覆盖。
7. 删除复制相关
7.1 fs.rm(path[, options], callback)
异步地删除文件和目录
// options recursive: true执行递归删除
fs.rm(path.join(path.resolve(), 'cssFiles'), {recursive: true}, (err) => {
if (err) {
return;
}
console.log('删除成功')
})
7.2 fs.rmdir(path[, options], callback)
异步的删除目录
// 仅能删除空文件夹
fs.rmdir(path.join(path.resolve(), 'cssFiles'), (err) => {
if (err) {
console.log(err)
return;
}
console.log('删除成功')
})
// 递归删除-传入参数options recursive: true
fs.rmdir(path.join(path.resolve(), 'cssFiles'), {recursive: true}, (err) => {
if (err) {
console.log(err)
return;
}
console.log('删除成功')
})
7.3 fs.unlink(path, callback)
异步地删除文件或符号链接。
fs.unlink(path.join(testDir, 'abc.css'), (err) => {
if (err) {
return;
}
console.log('文件删除成功')
})
7.4 fs.copyFile(src, dest[, mode], callback)
异步地将 src 复制到 dest
fs.copyFile(path.join(testDir, 'text.txt'), path.join(path.resolve(), 'static', 'copyText.txt'), (err) => {
if (err) {
console.log(err);
return;
}
console.log('复制成功')
})
注意:
- 只能复制文件,不能复制目录
- 目标目录不存在,不会创建,报错
- 文件类型可以不一致。
8. 重命名相关
8.1 fs.rename(oldPath, newPath, callback)
将 oldPath 处的文件异步重命名为作为 newPath 提供的路径名。
fs.rename(path.join(testDir, 'newText.txt'), path.join(testDir, 'reText.txt'), (err) => {
if (err) {
console.log(err);
return;
}
console.log('重命名成功')
})
注意:
- 不能重命名目录,只能重命名文件
- 如果重命名文件已存在将被覆盖
10. 监听文件变化
10.1 fs.watch(filename[, options][, listener])
监视 filename 的变化,其中 filename 是文件或目录。
10.2 fs.watchFile(filename[, options], listener)
监视filename文件的变化,只能是文件
11. 文件流相关
文件流,顾名思义就是以流(stream)的形式操作文件。文件流的方式适合于对大文件的读写操作。
- fs继承于stream
举个例子理解:
- 想象一下,如果把文件读取比从一个水池向一个池子里抽水,同步会阻塞程序,异步会等待结果(等待池子里面的水抽完),如果这个池子特别大怎么办?有三峡水库那么大怎么办?你要等到多久才能喝到抽的水?
- 因此便会有了文件流,文件流就好比你一边抽一边取,不用等池子满了再用一样方便。
11.1 fs.createReadStream(path[, options])
以流的形式读取文件
11.2 fs.createWriteStream(path[, options])
以流的形式写入文件
// 流的形式读取和写入,下面以复制文件的形式距离说明
const fileSource = path.join(path.resolve(), 'files/text.txt');
const pipeDest = path.join(path.resolve(), 'files/pipeCopy.txt');
// 创建可读流
const readStream = fs.createReadStream(fileSource);
// 创建可写流
const writePipeStream = fs.createWriteStream(pipeDest);
// pipe管道消费数据流
readStream.pipe(writePipeStream);