来源:Node.js

fs

Nodejs中,fs模块即文件系统模块(File System module)提供了与文件系统进行交互的各种功能

fs模块是Nodejs核心API之一,可以执行读取文件、写入文件、更改文件权限、创建目录等操作

选项

可以使用配置项flag指定一些操作方式

  • 'a':打开文件进行追加,如果文件不存在,则创建该文件
  • 'ax':类似于'a',但如果路径存在则失败。
  • 'a+':打开文件进行读取和追加,如果文件不存在,则创建该文件
  • 'ax+':类似于'a+',但如果路径存在则失败
  • 'as':以同步模式打开文件进行追加,如果文件不存在,则创建该文件
  • 'as+':以同步模式打开文件进行读取和追加,如果文件不存在,则创建该文件
  • 'r':打开文件进行读取,如果文件不存在,则发生异常
  • 'r+':打开文件进行读写,如果文件不存在,则发生异常
  • 'rs+':以同步模式打开文件进行读写,指示操作系统绕过本地文件系统缓存
    • 主要用于在NFS挂载上打开文件,因为它允许跳过可能过时的本地缓存
    • 它对I/O性能有非常实际的影响,因此除非需要,否则不建议使用此标志
    • 它不会将fs.open()fsPromises.open()变成同步阻塞调用,如果需要同步操作,应该使用类似 fs.openSync() 的东西。
  • 'w':打开文件进行写入,创建(如果它不存在)或截断(如果它存在)该文件
  • 'wx':类似于'w',但如果路径存在则失败
  • 'w+':打开文件进行读写,创建(如果它不存在)或截断(如果它存在)该文件
  • 'wx+':类似于'w+',但如果路径存在则失败

读取文件

fs返回的是一个buffer二进制数据,每两个十六进制数字表示一个字节

1
<Buffer e4 bd a0 e7 88 b1 e6 88 91 ef bc 8c e6 88 91 e7 88 b1 e4 bd a0 ef bc 8c e8 9c 9c e9 9b aa e5 86 b0 e5 9f 8e e7 94 9c e8 9c 9c e8 9c 9c>

想要获取文字,需要进一步调用toString方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const fs = require('node:fs')
const fs2 = require('node:fs/promises')

// 异步读取文件
fs.readFile('./index.txt', {
encoding: 'utf-8', // 编码方式
flag: 'r' // 读取选项
}, (err, data) => {
if (err) throw err
console.log(data.toString(), 111)
})

// 同步读取文件,会阻塞下面的代码
const data = fs.readFileSync('./index.txt')
console.log(data.toString(), 222)

// promise读取文件
fs2.readFile('./index.txt').then(data => {
console.log(data.toString(), 333)
}).catch(err => {
console.log(err)
})

流式读取文件,适用于读取大文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const fs = require('node:fs')
const fs2 = require('node:fs/promises')

// 流式读取文件
// 会一段一段地读取文件而非一下子读完,就可以慢慢处理数据
const readStream = fs.createReadStream('index.txt')

readStream.on('data', (chunk) => {
console.log(chunk.toString())
})

readStream.on('end', () => {
console.log('读取完成~')
})

写入文件

命名方式和读取文件一样,带有Sync字样的是同步方法,不带则是异步方法,下同

1
2
3
4
5
6
7
8
9
10
11
12
const fs = require('node:fs')

// 写入文件,但这会覆盖文件本来的内容
fs.writeFileSync('index.txt', '早上好中国,现在我有蜜雪冰城')

// 想要追加内容,需要添加flag
fs.writeFileSync('index.txt', '\n我很喜欢冰淇淋,但是,雪王驾到', {
flag: 'a'
})

// 或者使用appendFileSync,也是追加内容
fs.appendFileSync('index.txt', '\n你碍我,我碍你,你学编程天灭你~')

同样,也可以流式写入文件

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
const fs = require('node:fs')

let data = [
'Hello World1',
'Hello World2',
'Hello World3',
'Hello World4',
'Hello World5',
'Hello World6',
'Hello World7',
'Hello World8',
'Hello World9',
'Hello World10'
]

let writeStream = fs.createWriteStream('index.txt')

data.forEach((item) => {
writeStream.write(item + '\n')
})

// 别忘了关闭写入流
writeStream.end()

writeStream.on('finish', () => {
console.log('写入完成')
})

文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fs = require('node:fs')

// 创建文件夹
fs.mkdirSync('./test')

// 想要递归创建文件夹,需要使用recursive
fs.mkdirSync('./test2/foo/bar', { recursive: true })

// ---------------------------------------------

// 删除文件夹
fs.rmSync('./test')

// 想要递归删除文件夹,需要使用recursive
fs.rmSync('./test2', { recursive: true })

重命名

1
2
3
4
5
const fs = require('node:fs')

// 重命名
// 参数1: 原始文件名 参数2:新文件名
fs.renameSync('index.txt', 'index2.txt')

监听文件变化

1
2
3
4
5
6
7
const fs = require('node:fs')

// 监听文件变化
fs.watch('./index2.txt', (event, filename) => {
// 回调参数1:事件名 回调参数2:被监听的文件
console.log(event, filename)
})

创建硬/软链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const fs = require('node:fs')

// 创建硬链接
// 硬链接共享同一个内存地址,就像共享文件/备份文件
// 参数1:原文件地址 参数2:硬链接后的地址
fs.linkSync('index.txt', 'index2.txt')

// 创建软链接,又称符号链接
// 软链接就是一个指向原文件的指针,就像快捷方式
// 如果原文件被修改,软链接文件也会被修改;如果原文件被删除,软链接文件也会被删除
// 创建软链接需要管理员权限
// 硬链接共享同一个内存地址,就像共享文件/备份文件
// 参数1:原文件地址 参数2:硬链接后的地址
fs.symlinkSync('index.txt', 'index3.txt')

注意事项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fs = require('node:fs')

// 异步读取文件
fs.readFile('./index.txt', 'utf8', (err, data) => {
if (err) {
console.error(err)
return
}
console.log(data)
})

// setImmediate,在本轮事件循环的末尾执行函数
setImmediate(() => {
console.log('immediate')
})

按理说,异步读取文件的操作也会被推入本轮事件循环,在setImmediate之前执行,但结果却是这样

因为所有fs操作都由libuv调度,而所有计时器都由v8引擎调度

当libuv完成任务后,才会将其推入V8的事件队列,所以它会延后执行