对文件的I/O操作,简单来说就是用Node来操作系统中的文件。
先了解一些文件常识
权限位 mode
fs 模块需要对文件进行操作,会涉及到操作权限的问题。
文件操作权限分为读、写和执行,数字表示为八进制数,具备权限的八进制数分别为 4、2、1,不具备权限为 0。
使用ls -al
命令查看当前目录下文件权限信息
huhao@huhaodeMacBook-Pro node-study % ls -al
total 8
drwxr-xr-x 4 huhao staff 128 10 15 11:38 .
drwxr-xr-x@ 10 huhao staff 320 10 14 19:36 ..
-rw-r--r-- 1 huhao staff 1042 10 15 13:12 index.js
drwxr-xr-x 4 huhao staff 128 10 15 12:27 test
第一位代表是文件还是文件夹,d 开头代表文件夹,- 开头的代表文件,而后面是用户的权限位,按每三位划分,分别代表读(r)、写(w)和执行(x),- 代表没有当前位对应的权限。
比如:
-rw-r--r-- 1 huhao staff 1042 10 15 13:12 index.js
在mac系统权限 mode 默认是可读、可写、不可执行,所以权限位数字表示为 0o666
,转换十进制表示为 438。
标识位 flag
Node.js 中,标识位代表着对文件的操作方式,如可读、可写、即可读又可写等等.
-
‘r’ - 以读取模式打开文件。如果文件不存在则发生异常。
-
‘r+’ - 以读写模式打开文件。如果文件不存在则发生异常。
-
‘rs+’ - 以同步读写模式打开文件。命令操作系统绕过本地文件系统缓存。
-
‘w’ - 以写入模式打开文件。文件会被创建(如果文件不存在)或截断(如果文件存在)。
-
‘wx’ - 类似 ‘w’,但如果 path 存在,则失败。
-
‘w+’ - 以读写模式打开文件。文件会被创建(如果文件不存在)或截断(如果文件存在)。
-
‘wx+’ - 类似 ‘w+’,但如果 path 存在,则失败。
-
‘a’ - 以追加模式打开文件。如果文件不存在,则会被创建。
-
‘ax’ - 类似于 ‘a’,但如果 path 存在,则失败。
-
‘a+’ - 以读取和追加模式打开文件。如果文件不存在,则会被创建。
-
‘ax+’ - 类似于 ‘a+’,但如果 path 存在,则失败。
r+ 和 w+ 的区别,当文件不存在时,r+ 不会创建文件,而会抛出异常,但 w+ 会创建文件;如果文件存在,r+ 不会自动清空文件,但 w+ 会自动把已有文件的内容清空。
文件描述符 fs
操作系统会为每个打开的文件分配一个名为文件描述符的数值标识,文件操作使用这些文件描述符来识别与追踪每个特定的文件。
在 Node.js 中,每操作一个文件,文件描述符是递增的,文件描述符一般从 3 开始,因为前面有 0、1、2 三个比较特殊的描述符,分别代表 process.stdin
(标准输入)、process.stdout
(标准输出)和 process.stderr
(错误输出)。
引入
要使用文件系统模块,需要引入文件系统模块fs
。
const fs = require('fs');
Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本。比如说读取文件函数异步读取 fs.readFile()
和同步读取 fs.readFileSync()
。
创建目录
异步地创建目录:fs.mkdir(path,callback)
path:创建目录路径名称
callback:回调函数,回调只有一个可能的异常参数(error),如果error为null,则创建成功,否则失败。
// 创建目录
fs.mkdir("./test", (err) => {
console.log(err)
if (err && err.code === 'EEXIST') {
console.log('目标已存在')
return
}
console.log('创建成功')
})
同步地创建目录:fs.mkdirSync(path)
修改目录名称
异步:fs.rename(oldPath, newPath, callback)
oldPath:需要修改的目录路径
newPath:修改之后的目录路径
callback:回调函数,回调只有一个可能的异常参数(error),如果error为null成功,否则失败。
fs.rename("./test", "./test2", (err) => {
console.log(err)
if (err && err.code === 'ENOENT') {
console.log('目录不存在')
}
console.log('修改成功')
})
同步:fs.renameSync(oldPath, newPath)
删除空的目录
异步:fs.rmdir(path, callback)
path:删除的目录路径
callback:回调函数,回调只有一个可能的异常参数(error),如果error为null成功,否则失败。
Note:如果目录不为空不会直接删除,要先删除文件才能删除目录。
fs.rmdir("./test2", err => {
if (err && err.code === 'ENOENT') {
console.log('目录不存在')
}
console.log('删除成功')
})
同步:fs.rmdirSync(path)
读取目录
异步:fs.readdir(path[, options], callback)
读取一个目录的内容
path:目录路径
options:可选的 options 参数可以是指定编码的字符串,也可以是具有 encoding 属性的对象,encoding默认 = ‘utf8’,如果 encoding 设为 ‘buffer’,则返回的文件名会被作为 Buffer 对象传入。如果 options.withFileTypes 设置为 true,则 files 数组将包含 fs.Dirent 对象。
callback:有两个参数 (err, files),其中 files中放的是读取目录中的文件和子目录信息, 其中不包括 ‘.’ 和 ‘…’ 的文件名的list。
注意: ‘path’ 的路径是以当前文件为基准进行查找的,而不是运行的时候的相对路径
fs.readdir("./test", (err, files) => {
if (err) {
console.log('读取失败')
}
console.log('成功',files)
})
同步fs.readdirSync(path[, options])
const dirs = fs.readdirSync("./test")
console.log('成功',dirs)
打开文件
同步:fs.openSync(path[, flags[, mode]])
path:要打开文件的路径
flags:打开文件要做的操作类型:r:只读,w:可写的
mode: 设置文件的操作权限,一般不传
该方法会返回一个文件的描述符作为结果,我们可以通过该描述符来对文件进行各种操作。
var fd = fs.openSync('./test/hello.txt', 'w')
console.log(fd)//20
20
就是该文件的编号
异步;fs.open(path, flags[, mode], callback)
callback:是必须的回调函数。
- err:错误对象,如果没有错误则为null
- fd:文件的描述符
var fd = fs.open('./test/hello.txt', 'w',function (err,fd){
if (err) throw err;
console.log("成功",fd) //成功 20
})
write向文件写内容
使用缓冲区
fs.write(fd, buffer[, offset[, length[, position]]], callback)
写入 buffer到 fd 指定的文件。
使用字符串
fs.write(fd, string[, position[, encoding]], callback)
写入 string 到 fd 指定的文件。
参数:
fd:文件描述符是使用fs.open()方法打开文件所返回的值。它包含一个整数值。
buffer:它包含缓冲区类型值,例如Buffer,TypedArray,DataView。
offset:它是一个整数值,用于确定要写入文件的缓冲区部分。
length:它是一个整数值,用于指定要写入文件的字节数。
position:它是一个整数值,其位置是指距要写入数据的文件开头的偏移量,如果 typeof position !== ‘number’,则数据从当前位置写入。
callback:它包含回调函数,该函数接收错误和写入文件的字节数。
string:将字符串写入fd指定的文件。
encoding:默认编码值为UTF-8。
callback:回调函数接收到错误(err)或写入的字节数(writtenbytes)。如果收到错误,则打印错误消息,否则打印写入的字节数。
写入 buffer
let buffer = new Buffer.from('测试一下');
//'a' 以追加模式打开文件
fs.open('./test/hello.txt', 'a', function(err, fd) {
if(err) {
console.log('打开失败');
}else {
fs.write(fd, buffer, 0, buffer.length, 2, function(err,writtenbytes) {
if(err) {
console.log('写入失败');
}else {
console.log('成功',writtenbytes); //成功 12
}
})
}
})
./test/hello.txt
内容
he测试一下
成功执行程序后,缓冲区中存储的数据将附加到所需文件中。如果该文件事先不存在,则会创建一个新文件并将数据附加到该文件中。
写入 string
const str = "Hello world";
fs.open('./test/hello.txt', 'a', function(err, fd) {
if(err) {
console.log('打开失败');
}else {
fs.write(fd, str, function(err,bytes) {
if(err) {
console.log('写入失败');
}else {
console.log('成功',bytes);
}
})
}
})
./test/hello.txt
内容`
he测试一下Hello world
程序成功执行后,字符串值被写入(附加)到所需文件中。
同步:fs.writeSync(fd, string[, position[, encoding]])
写入的内容默认会覆盖问文件原本的内容
var fd = fs.openSync('./test/hello.txt', 'w')
fs.writeSync(fd,"天气真不错")
./test/hello.txt
内容`
天气真不错
写入文件后关闭文件的描述符
如果文件不关闭会占用大量的内存
同步:fs.closeSync(fd)
var fd = fs.openSync('./test/hello.txt', 'w')
fs.writeSync(fd, "天气真不错")
fs.closeSync(fd)
异步:fs.close(fd[, callback])
const str = "Hello world";
fs.open('./test/hello.txt', 'a', function(err, fd) {
if(err) {
console.log('打开失败');
}else {
fs.write(fd, str, function(err,bytes) {
if(err) {
console.log('写入失败');
}else {
console.log('成功',bytes);
// 关闭
fs.close(fd,function (err){
if (!err) {
console.log("文件关闭成功")
}
})
}
})
}
})
writeFile写入数据到文件并创建文件
writeFile
是对打开、写入和保存文件的封装,就不用在执行打开和关闭文件操作。
异步:fs.writeFile(file, data[, options], callback)
,如果文件已经存在,则替代文件。
file:文件名
data:文件内容 <string> | <Buffer> | <Uint8Array>
options: <Object> | <string>
- encoding:
<string> | <null>
默认 = ‘utf8’ - mode:
<integer>
默认 = 0o666 - flag:
<string>
默认 = ‘w’
callback:回调函数,异常参数(error)
fs.writeFile("./test/hello.txt", "hello", 'utf8', function (err) {
if (err) throw err;
console.log("写入成功")
})
注意:
多次对同一文件使用fs.writeFile
且不等待回调,是不安全的。 对于这种情况,强烈推荐使用fs.createWriteStream
。
如果 file 指定为一个文件描述符,则它不会被自动关闭。
同步:fs.writeFileSync(file, data[, options])
文件追加写入
异步:fs.appendFile(file, data[, options], callback)
异步地追加数据到一个文件,如果文件不存在则创建文件。 data 可以是一个字符串或 Buffer。
fs.appendFile('./test/hello.txt', '这是追加的数据', (err) => {
if (err) throw err;
console.log('成功');
});
如果文件通过 fs.open()
或者 fs.openSync()
打开用来追加数据的数字文件描述符,将不会被自动关闭。
fs.open('./test/hello.txt', 'a', (err, fd) => {
if (err) throw err;
fs.appendFile(fd, '这是追加的数据2', 'utf8', (err) => {
// 关闭
fs.close(fd, (err) => {
if (err) throw err;
});
if (err) throw err;
});
});
同步:fs.appendFileSync(file, data[, options])
流式文件写入
流式文件写入就是一次向文件中写入一点,不会造成内存溢出。
如同步、异步、简单文件的写入都是一次性的写入,即以一次性就将所有内容从一个文件写入到另一个文件。缺点是都不适合大文件的写入,性能较差,容易导致内存溢出。
fs.createWriteStream(path[, options])
path:文件路径
options:配置参数
options是一个带有以下默认值的对象或字符串:
const defaults = {
flags: 'w',
encoding: 'utf8',
fd: null,
mode: 0o666,
autoClose: true
};
options 也可以包括一个 start 选项,使其可以写入数据到文件某个位置。 如果是修改一个文件而不是覆盖它,则需要flags 模式为 r+ 而不是默认的 w 模式。 encoding 可以是任何可以被 Buffer 接受的值。
如果 autoClose 被设置为 true(默认),则在 error 或 end 时,文件描述符会被自动关闭。 如果 autoClose 为 false,则文件描述符不会被关闭,即使有错误。 应用程序需要负责关闭它,并且确保没有文件描述符泄漏。
const str = '写入流'
//1. 创建写入流
let writeStream = fs.createWriteStream('./test/hello.txt',{
flags: 'r+',
encoding: 'utf8',
fd: null,
mode: 0o666,
autoClose: true
})
//2.设置写入流的字符集:先写入缓存
writeStream.write(str)
writeStream.write('\n不会被覆盖')
//3.写入结束
writeStream.end()// 不能用ws.close()
//4.触发打开
writeStream.once("open",function (){
console.log("流打开了")
})
//5.触发finish事件
writeStream.on('finish',function(){
console.log('写入完成')
})
//6.触发error
writeStream.on('error',(err)=>{
console.log(err.stack)
})
读取文件
异步fs.readFile(path[, options], callback)
path:要读取的文件的路径
options:读取的选项
callback:回调函数,通过回调函数将读取到内容返回
回调函数有两个参数:err,data
data
是读取到的数据是一个buffer
fs.readFile("./test/hello.txt",function (err,data){
if (err) throw err;
console.log("文件内容",data.toString())
})
同步fs.readFileSync(path[, options])
let data = fs.readFileSync('./test/hello.txt')
console.log('文件内容是:',data.toString())
流式文件读取
流式文件读取也适用于一些比较大的文件,可以分多次将文件读取到内存中
fs.createReadStream(path[, options])
不同于在一个可读流上设置的 highWaterMark 默认值(16 kb),该方法在相同参数下返回的流具有 64 kb 的默认值。
options 对象或字符串:
const defaults = {
flags: 'r',
encoding: null,
fd: null,
mode: 0o666,
autoClose: true,
highWaterMark: 64 * 1024
};
options 可以包括 start 和 end 值,使其可以从文件读取一定范围的字节而不是整个文件。 start 和 end 都是包括在内的,并且起始值是 0。 如果指定了 fd 且 start 不传或为 undefined,则 fs.createReadStream() 从当前文件位置按顺序地读取。
如果 autoClose 为 false,则文件描述符不会被关闭,即使有错误。 应用程序需要负责关闭它,并且确保没有文件描述符泄漏。 如果 autoClose 被设置为 true(默认),则在 error 或 end 时,文件描述符会被自动关闭。
mode 用于设置文件模式(权限和粘结位),但仅限创建文件时。
如果 options 是一个字符串,则它指定了字符编码。
var rs = fs.createReadStream("./test/hello.txt")
// 这里是文本才设置为 utf8 就会打印出文件列表
//如果不设置则打印出buffer
rs.setEncoding('utf8')//如果设置成ascii,则会乱码
// 这里文件比较小所以只读取了一次,如果文件较大,这里会执行多次。
rs.on("data",function (data){
console.log(data)
})
rs.once("open",function (){
console.log("流打开了")
})
rs.on("close",function (){
console.log("流关闭了")
})
输出
//设置为 utf8
流打开了
写入流
不会被覆盖
流关闭了
// 未设置 utf8
流打开了
<Buffer e5 86 99 e5 85 a5 e6 b5 81 0a e4 b8 8d e4 bc 9a e8 a2 ab e8 a6 86 e7 9b 96>
流关闭了
流式文件实现文件的读取和写入
用于从一个流中获取数据,并通过该流输出到另一个流.
//1.创建一个输入流:用于读取数据
let readStream = fs.createReadStream('./test/hello.txt')
//2.创建一个输出流:用于写数据
let writeStream = fs.createWriteStream('./test/output.txt')
//3.通过输入流调用方法:将数据送入输出流中
readStream.pipe(writeStream)
流式文件实现压缩/解压缩文件的读取和写入
需要引入模块
const zlib = require('zlib') //压缩与解压缩的模块
压缩文件
const zlib = require('zlib') //压缩与解压缩的模块
const createZip = zlib.createGzip()//创建压缩对象
const writeStream = fs.createWriteStream('./hello.zip')//生成一个.zip的压缩文件
fs.createReadStream('./test/hello.txt')
.pipe(createZip)
.pipe(writeStream)
解压缩
const createZip = zlib.createGunzip()//创建一个解压缩的对象。
const writeStream = fs.createWriteStream('./test/hello2.txt')
fs.createReadStream('./hello.zip')
.pipe(createZip) //通过管道连接到解压缩对象
.pipe(writeStream) //通过管道连接输出流
删除文件
异步fs.unlink(path, callback)
fs.unlink("./test/hello2.txt",err=> {
if (err) throw err;
console.log("删除成功")
})
同步fs.unlinkSync(path)
删除非空目录
同步删除
fs.readdir("./test", (err, data) => {
if (err) throw err;
console.log(data)//[ 'hello.txt', 'output.txt', 'test1.txt' ]
data.forEach(item => {
// 使用同步方法进行删除,以为嫩文件删除完才能删除目录
fs.unlinkSync(`./test/${item}`)
})
fs.rmdir("./test",(err)=>{
if (err) throw err;
console.log('删除目录成功')
})
})
由于JS是单线程的所以一旦同步阻塞整个网页就卡在那里了
异步删除
fs.readdir("./test").then(async (data) => {
let arr=[]
data.forEach(item => {
arr.at.push(fs.unlink(`./test/${item}`))
})
// Promise.all([]),等待数组中的内容都执行完才往下执行
await Promise.all(arr)
await fs.rmdir("./test")
})
文件信息
异步fs.stat(path, callback)
它会给我们返回一个对象,这个对象中保存了当前对象状态的相关信息。
fs.stat("./test",function (err,stat){
console.log(stat)
})
输出:
Stats {
dev: 16777225,
mode: 16877,
nlink: 3,
uid: 501,
gid: 20,
rdev: 0,
blksize: 4096,
ino: 56324839,
size: 96,
blocks: 0,
atimeMs: 1697360349332.8914,
mtimeMs: 1697360349333.1167,
ctimeMs: 1697360349333.1167,
birthtimeMs: 1697360349332.8914,
atime: 2023-10-15T08:59:09.333Z,
mtime: 2023-10-15T08:59:09.333Z,
ctime: 2023-10-15T08:59:09.333Z,
birthtime: 2023-10-15T08:59:09.333Z
}
不建议在调用 fs.open()
、fs.readFile()
或 fs.writeFile()
之前使用 fs.stat()
检查一个文件是否存在。
stats
的常用属性:
- stats.size文件的大小
- stats.isFile()是否是一个文件
- stats.isDirectory()是否是一个文件爽
fs.stat("./test",function (err,stat){
console.log(stat.isFile())// false
console.log(stat.isDirectory()) // true
})
同步fs.statSync(path)
监测文件变化
fs.watch( filename[, options][, listener] )
- filename:它是一个字符串,Buffer或URL,表示要监视的文件或目录的名称。
- options:它是可用于修改方法行为的字符串或对象。它是一个可选参数。它具有以下参数:
persistent:它是一个布尔值,用于指定只要正在监视文件,该过程是否应继续。默认值是true。
recursive:它是一个布尔值,用于指定是否应监视给定目录的所有子目录。默认值为false。
encoding:它是一个字符串,它指定用于传递给侦听器的文件名的字符编码。 - listener:它是在访问或修改文件时调用的函数。它是一个可选参数。
eventType:它是一个字符串,它指定文件进行的修改的类型。
filename:它是一个字符串或Buffer,它指定触发事件的文件名。
文件上watch()方法的用法
fs.watch("./test/hello.txt", (eventType, filename) => {
console.log("\n", filename, "修改成功");
console.log("修改后:", eventType);
});
// 修改文件名称
setTimeout(
() => fs.renameSync("./test/hello.txt", "./test/hello3.txt"),
1000
);
输出
hello3.txt 修改成功
修改类型: rename
hello3.txt 修改成功
修改类型: change