NodeJS之fs文件系统模块的理解
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);
作者:ktb07
链接:https://juejin.cn/post/7039160563393363981