[TOC]
>[success] # 文件读取api
| API | 优点 | 缺点 |
| --- | --- | --- |
| `fs.readFileSync` | 简单易用 | 阻塞式 I/O,可能会影响应用程序的性能 |
| `fs.readFile` | 非阻塞式 I/O,不会影响应用程序的性能 | 需要使用回调函数处理读取到的数据 |
| `fs.createReadStream` | 适合读取大型文件,不会将整个文件加载到内存中,可节省内存 | 需要使用 `pipe` 方法将数据流导向其他流或响应对象 |
| `fs.promises.readFile` | 支持 Promise,使用 async/await 可以编写更加简洁的异步代码 | 只在 Node.js 10.x 版本及以上支持 |
| `fs.read` | 可以更加精细地控制读取文件的过程,例如指定读取的起始位置和长度 | 使用较为繁琐,需要手动处理读取到的数据 |
需要注意的是,选择使用哪种 API 取决于具体的场景和需求。例如,如果需要读取大型文件而又不想将整个文件加载到内存中,就可以使用 `fs.createReadStream`。如果需要编写简洁易读的异步代码,就可以使用 `fs.promises.readFile` 或者使用第三方模块 `fs-extra` 中提供的 `fs.readFileAsync`(在 Node.js 8.x 版本及以上支持)等等。
如果你的 Node.js 版本不低于 8.x,那么你可以使用第三方模块 `fs-extra` 中提供的 `fs.readFileAsync` 方法来读取文件。`fs-extra` 是一个扩展了 Node.js 原生 fs 模块功能的第三方模块,相较于原生 fs 模块,它提供了更加方便的 API,并且在使用上也更加简单。`fs-extra` 中的 `fs.readFileAsync` 方法与 Node.js 原生的 `fs.promises.readFile` 方法类似,都是 Promise 形式的异步读取文件方法,用法也相
>[info] ## 文件读取须知
1. 当我们需要读取一个文件时,可以使用`Node.js`中的`fs`模块
* **fs.readFileSync(path\[, options\])**:同步读取文件内容,返回一个Buffer对象或字符串。
* **fs.readFile(path\[, options\], callback)**:异步读取文件内容,将内容作为回调函数的第二个参数返回。
2. 对一些flag 标签说明
* ` r`:以只读模式打开文件。
* `r+`:以读写模式打开文件,如果不存在那么抛出异常;。
* `'rs+'`: 以同步模式打开文件进行读写。 指示操作系统绕过本地文件系统缓存。
* `w`:以写入模式打开文件,如果文件存在,则覆盖该文件,如果文件不存在,则创建该文件。
* `wx`:类似于w,但是如果文件已经存在,则操作失败。
* `w+`:以读写模式打开文件,如果文件存在,则覆盖该文件,如果文件不存在,则创建该文件。
* `wx+`:类似于w+,但是如果文件已经存在,则操作失败。
* `a`:以追加模式打开文件,如果文件不存在,则创建该文件。写入内容在文章末尾
* `ax`:类似于a,但是如果文件已经存在,则操作失败。
* `a+`:以读取和追加模式打开文件,如果文件不存在,则创建该文件。写入内容在文章末尾
* `ax+`:类似于a+,但是如果文件已经存在,则操作失败。
* `'as'`: 以同步模式打开文件进行追加。 如果文件不存在,则创建该文件。
* `'as+'`: 以同步模式打开文件进行读取和追加。 如果文件不存在,则创建该文件
3. 对 encoding 一些类型说明
* `'utf8'`或'`utf-8'` - UTF-8编码格式
* `'utf16le'` - UTF-16LE编码格式
* `'latin1'`或`'iso-8859-1'` - ISO-8859-1编码格式
* ` 'base64'` - Base64编码格式
* ` 'hex' `- 十六进制编码格式 例如,我们可以使用以下代码以UTF-8编码格式读取文件内容:
4. **flag参数为**'w',但是这并不代表可以用它来进行写入操作。实际上,当flag参数设置为'w'时,只是表示以写入方式打开文件,但是并没有执行实际的写入操作,同理使用`a` 也是如此
>[danger] ### fs.readFileSync(path\[, options\]) 同步读取
* fs.readFileSync方法的参数及其说明,其中,**path参数是必需的**,用于指定要读取的文件的路径。**options参数是可选的**,用于指定读取文件的选项,其中encoding和flag属性也是可选的。**如果未指定options参数则是使用只读模式r并且返回一个Buffer对象,则默认以二进制格式读取文件内容**,**如果未指定encoding,则返回一个Buffer对象,则默认以二进制格式读取文件内容**,如果**未指定flag,则默认以只读模式(r)打开文件。**,
>[danger] ##### api 说明
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| path | string | 要读取的文件的路径 |
| options | object | 可选的配置对象,用于指定读取文件的选项 |
| options.encoding | string | 指定文件的编码格式,如果未指定则返回Buffer对象 |
| options.flag | string | 指定文件的打开方式,默认为'r',可选的打开方式包括:'r', 'r+', 'rs+', 'w', 'wx', 'w+', 'wx+', 'a', 'ax', 'a+', 'ax+' |
>[danger] ##### 案例
~~~
const fs = require('fs') // node 13 以后版本可以写成const fs = require('node:fs')
// 读取文件内容为 1111111111 文件路径为 ./a.txt
// 1.同步读取文件只读模式r并且返回一个Buffer对象,则默认以二进制格式读取文件内容
const data = fs.readFileSync('./a.txt')
console.log(data) // <Buffer 31 31 31 31 31 31 31 31 31 31>
console.log(data.toString()) // 1111111111 toString()方法默认是utf-8 格式 将Buffer对象转换为字符串
// 2. 同步读取文件规定返回编码格式utf-8
const data1 = fs.readFileSync('./a.txt', 'utf-8')
console.log(data1) // 1111111111
// 3. 同步读取文件规定返回编码和文件读取模式
const data2 = fs.readFileSync('./a.txt', { encoding: 'utf-8', flag: 'r' })
console.log(data1) // 1111111111
~~~
同步的 API 会阻塞 Node.js 事件循环和下一步的 JavaScript 执行,直到操作完成。 异常会被立即地抛出,可以使用`try…catch`来处理,也可以允许冒泡。
~~~js
const { readFileSync} = require('fs');
try {
readFileSync('/tmp/hello');
} catch (err) {
// 处理错误
}
~~~
>[danger] ### fs.readFile(path\[, options\], callback) 异步读取
1. `fs.readFile()`是Node.js文件系统模块中提供的一个异步文件读取函数
2. `fs.readFile()`函数会异步读取文件,当文件读取完成后,会调用回调函数,将文件内容传递给回调函数。如果读取文件失败,会将错误对象传递给回调函数。需要注意的是,如果文件较大,使用`fs.readFile()`函数可能会导致性能问题,可以使用`fs.createReadStream()`函数来读取文件流,并逐块处理读取的数据。
3. `path`参数表示要读取的文件的路径或文件的URL,可以是一个字符串、缓冲区或URL对象,也可以是一个文件描述符;`options`参数是一个可选的对象,包含编码方式、文件读取模式等等设置,或者是一个字符串,表示编码方式;`callback`参数是读取文件完成后的回调函数,回调函数有两个参数:`err`和`data`,其中`err`为Error对象,如果读取成功,`err`为null;`data`为文件的内容,如果指定了
4. **如果未指定options参数则是使用只读模式r并且返回一个Buffer对象,则默认以二进制格式读取文件内容**,**如果未指定encoding,则返回一个Buffer对象,则默认以二进制格式读取文件内容**,如果**未指定flag,则默认以只读模式(r)打开文件。**
>[danger] ##### api说明
| 参数 | 类型 | 描述 |
| --- | --- | --- |
| path | string | Buffer | URL | integer | 要读取的文件的路径或文件的URL,可以是一个字符串、缓冲区或URL对象,也可以是一个文件描述符。 |
| options | Object | string | 一个可选的参数对象,包含编码方式、文件读取模式等等设置,或者是一个字符串,表示编码方式。 |
| options.encoding | string | null | 字符编码方式。默认值为null,表示读取的是Buffer对象。如果指定了编码方式,则返回一个字符串。 |
| options.flag | string | 文件打开的模式。默认是'r',表示只读。具体的可选值请参考文件系统的flags支持。 |
| options.signal | AbortSignal | AbortSignal对象,用于在读取文件时中止读取操作。 |
| callback | Function | 读取文件完成后的回调函数,回调函数有两个参数:err和data,其中err为Error对象,如果读取成功,err为null;data为文件的内容,如果指定了options.encoding,则data为一个字符串,否则为一个Buffer对象。 |
>[danger] ##### 案例
~~~
const fs = require('node:fs')
// 读取文件内容为 1111111111 文件路径为 ./a.txt
// 1. 异步读取文件只读模式r并且返回一个Buffer对象,则默认以二进制格式读取文件内容
fs.readFile('./a.txt', (err, data) => {
if (err) {
console.log(err)
return
}
console.log(data) // <Buffer 31 31 31 31 31 31 31 31 31 31>
console.log(data.toString()) // 1111111111
})
// 2. 异步读取文件规定返回编码格式utf-8
fs.readFile('./a.txt', 'utf-8', (err, data) => {
if (err) {
console.log(err)
return
}
console.log(data) // 1111111111
})
// 3. 异步读取文件规定返回编码和文件读取模式
fs.readFile('./a.txt', { encoding: 'utf-8', flag: 'r' }, (err, data) => {
if (err) {
console.log(err)
return
}
console.log(data) // 1111111111
})
~~~
* `options.signal`是一个可选的参数,用于传递`AbortSignal`对象,用于在读取文件时中止读取操作。`AbortSignal`是一个可以用于终止某些操作的信号对象。当调用`AbortSignal.abort()`函数时,可以向一个或多个正在进行的异步操作发送一个中止信号以取消操作。如果在读取文件时传递了一个`AbortSignal`对象,并且在读取过程中接收到中止信号,那么`fs.readFile()`函数将停止读取文件,并立即调用回调函数,将一个AbortError对象传递给它。
~~~
const fs = require('fs')
// 创建一个中止控制器
const controller = new AbortController()
// 获取中止信号
const signal = controller.signal
// 读取文件
const options = { signal }
fs.readFile('./a.txt', options, (err, data) => {
if (err) {
// 如果是中止错误,打印错误信息
if (err.code === 'ABORT_ERR') {
console.log('File read operation aborted')
} else {
// 否则打印错误信息
console.error(err)
}
} else {
// 打印文件内容
console.log(`File contents: ${data}`)
}
})
// 在2秒后中止读取文件操作
setTimeout(() => {
controller.abort() // 中止读取文件操作
})
~~~
>[danger] ### fsPromises.readFile(path[, options])
`fsPromises.readFile(path[, options])` 是 Node.js v10.0.0 及以上版本中引入的 Promise 形式的异步读取文件 API,它返回一个 Promise 实例,可以通过 `await` 或 `then` 方法获取读取到的文件内容。**该方法和 `fs.readFile` 的功能相同,只不过不需要传递回调函数,而是直接通过 Promise 的方式处理异步操作的结果**
>[danger] ##### api说明
| 参数 | 类型 | 描述 |
| --- | --- | --- |
| `path` | `string` | `Buffer` | `URL` | `FileHandle` | 必需参数,文件名或 `FileHandle` 对象 |
| `options` | `Object` | `string` | 可选参数,可以是一个包含以下属性的对象或一个字符串 |
| `options.encoding` | `string` | `null` | 可选参数,指定文件编码。如果不指定编码,则返回一个 `Buffer` 对象 |
| `options.flag` | `string` | 可选参数,指定文件的打开方式。默认为 `'r'`,表示以读取模式打开文件 |
| `options.signal` | `AbortSignal` | 可选参数,允许中止正在进行的读取文件的操作 |
>[danger] ##### 案例
* 具体细节可参考**fs.readFile(path[, options], callback) 异步读取**
~~~
const fs = require('fs')
// const readFile= require('fs').promises
// const readFile= require('fs/promises')
fs.promises.readFile('./aaa.txt', {
encoding: 'utf-8'
}).then(res => {
console.log("获取到结果:", res)
}).catch(err => {
console.log("发生了错误:", err)
})
~~~
>[danger] ### fs.createReadStream(path[, options])
1. `fs.createReadStream`方法来**创建一个可读流,使用可读流的方式读取文件时,数据会被分成小块逐步读入内存,而不是一次性读取整个文件**,
2. 可以通过指定要读取的文件路径或者直接使用这个`fd`创建通过`fs.createReadStream` 创建`ReadStream`对象
3. 通过`ReadStream`对象,**我们可以从文件中读取数据,并将其转换为我们需要的格式。例如,我们可以读取一个文本文件,并将其转换为字符串,或者读取一个二进制文件,并将其转换为Buffer对象**
4. 创建一个`ReadStream`对象时,它会自动将文件分成一块块的数据进行读取。可以通过设置`highWaterMark`属性来控制每块数据的大小。**当我们读取的数据量超过`highWaterMark`大小时,`ReadStream`对象将自动暂停读取**,等待我们处理已经读取的数据。当我们处理完数据后,`ReadStream`对象会自动恢复读取,继续读取下一块数据
5. 当我们不再需要`ReadStream`对象时,应该及时销毁它,以免造成资源浪费。销毁`ReadStream`对象后,它会自动关闭文件描述符,触发'close'事件。如果我们希望在销毁对象时不触发'close'事件,可以设置`emitClose`选项为`false`。
6. 一些优缺点对照表
| 优点 | 缺点 |
| --- | --- |
| 内存使用效率高,减少内存使用量 | 实现复杂,需要处理多个事件和回调函数 |
| 速度快,避免一次性将整个文件读入内存的延迟 | 可读性差,代码结构较复杂,可读性相对较差 |
| 数据处理灵活,可以逐块处理数据 | 不适合小文件,可能会增加代码的复杂性,不会带来很大的性能优势 |
| 可以控制读取的范围,读取部分内容 | 可靠性差,可能出现数据丢失或不完整,需要处理错误和异常情况 |
>[danger] ##### api 说明
`api` 参数说明,当`options.autoClose`设置为`true`时,当读取流结束时会自动关闭底层文件描述符(file descriptor)或文件句柄(file handle),也就是说,当读取流读取完所有数据后,底层的文件描述符或文件句柄将被自动关闭。
当`options.emitClose`设置为`true`时,读取流会在底层文件描述符或文件句柄关闭时触发 'close' 事件,这个事件触发后,读取流的 'close' 事件也会被触发。如果`options.emitClose`设置为`false`,则不会触发 'close' 事件。注意,即使`options.emitClose`设置为`false`,当读取流结束时,底层的文件描述符或文件句柄仍然会被自动关闭,除非`options.autoClose`设置为`false`。
`options.fs`参数是一个可选的文件系统对象,这个参数通常用于测试或开发环境中后续用到在了解
| 参数名 | 类型 | 描述 | 默认值 |
| --- | --- | --- | --- |
| path | string | Buffer | URL | 要读取的文件的路径或文件描述符 | 无 |
| options | string | Object | 可选对象,可以包含以下属性 | 无 |
| options.flags | string | 请参阅支持的文件系统标志 | 'r' |
| options.encoding | string | 编码格式 | null |
| options.fd | integer | FileHandle | 文件描述符 | null |
| options.mode | integer | 文件的权限掩码 | 0o666 |
| options.autoClose | boolean | 自动关闭标志 | true |
| options.emitClose | boolean | 关闭事件标志 | true |
| options.start | integer | 起始位置(以字节为单位) | 0 |
| options.end | integer | 结束位置(以字节为单位) | Infinity |
| options.highWaterMark | integer | 每次读取的最大字节数 | 64 \* 1024 |
| options.fs | Object | null | 文件系统对象 | null |
>[danger] ##### 案例
| **事件** | **描述** |
| --- | --- |
| `data` | 当流读取到数据时触发。 |
| `end` | 当流读取完所有数据时触发。 |
| `error` | 当流发生错误时触发。 |
~~~
const fs = require('fs')
// 1. 获取readStream 流
const stream = fs.createReadStream('./b.txt')
console.log(12)
// 2. 监听流的data事件
stream.on('data', (chunk) => {
console.log(chunk.toString())
})
// 3. 监听流的end事件
stream.on('end', () => {
console.log('end')
})
// 4. 监听流的error事件
stream.on('error', (err) => {
console.log(err)
})
// 5. 监听流的close事件
stream.on('close', () => {
console.log('close')
})
// 6. 监听流的open事件
stream.on('open', () => {
console.log('open')
})
~~~
* 使用 fd 读取文件
~~~
const fs = require('fs')
const fd = fs.openSync('./a.txt')
// 1. 获取readStream 流
const stream = fs.createReadStream(null, { fd, highWaterMark: 3 })
console.log(12)
// 2. 监听流的data事件
stream.on('data', (chunk) => {
console.log(chunk.toString())
})
// 3. 监听流的end事件
stream.on('end', () => {
console.log('end')
})
// 4. 监听流的error事件
stream.on('error', (err) => {
console.log(err)
})
// 5. 监听流的close事件
stream.on('close', () => {
console.log('close')
})
// 6. 监听流的open事件
stream.on('open', () => {
console.log('open')
})
~~~
>[danger] ##### 案例二
| **方法** | **描述** |
| --- | --- |
| `pause()` | 用于暂停可读流的数据读取,调用该方法后,可读流将不会再触发 'data' 事件,直到调用`resume()`方法恢复数据读取。该方法通常用于暂停数据读取,以便进行某些操作或等待其他事件发生。 |
| `resume()` | 用于恢复可读流的数据读取,调用该方法后,可读流将继续触发 'data' 事件,从而读取数据。该方法通常用于恢复数据读取,以便继续处理数据。 |
| `pipe()` | 用于将一个可读流连接到一个可写流,将可读流中的数据输出到可写流中。该方法会自动管理数据流动,从而实现高效的数据传输。该方法还可以接收一个可选的参数,用于控制数据传输的方式和行为,例如是否自动关闭可写流、是否允许多个可写流等 |
| `unpipe()` | 用于停止将可读流中的数据输出到另一个可写流中,取消数据传输。可以将一个可写流对象作为参数传递给该方法,表示只取消将指定的可写流对象中的数据输出,如果不传递参数,则表示取消所有的数据输出。该方法通常用于在数据传输过程中动态取消数据输出,以便进行其他操作或处理错误。 |
* 关于方法使用,这里只是列举部分更多详细需要看`ReadStream` api
*****
用可读流和可写流压缩文件
~~~js
const fs = require('fs');
const zlib = require('zlib');
const readableStream = fs.createReadStream('source.txt');
const gzipStream = zlib.createGzip();
const writableStream = fs.createWriteStream('destination.txt.gz');
readableStream.pipe(gzipStream).pipe(writableStream);
~~~
使用`fs`模块的`createReadStream`方法创建可读流对象`readableStream`,并将其指向源文件`source.txt`。然后,我们在`data`事件中调用`pause`方法暂停数据读取,并在 1 秒钟后调用`resume`方法恢复数据读取
~~~
const fs = require('fs');
const readableStream = fs.createReadStream('source.txt');
readableStream.on('data', (chunk) => {
console.log(chunk.toString());
readableStream.pause();
setTimeout(() => {
readableStream.resume();
}, 1000);
});
~~~
使用 unpipe 方法取消数据输出,`fs`模块的`createReadStream`方法创建可读流对象`readableStream`,并将其指向源文件`source.txt`。同时,我们使用`createWriteStream`方法创建两个可写流对象`writableStream1`和`writableStream2`,并将其分别指向两个目标文件`destination1.txt`和`destination2.txt`。然后,我们使用`pipe`方法将可读流和两个可写流连接起来,将源文件中的数据拷贝到两个目标文件中。最后,我们在 1 秒钟后调用`unpipe`方法取消了其中一个数据输出流的连接,即将可读流和`writableStream1`的连接断开,使得数据只输出到`writableStream2`中
~~~
const fs = require('fs');
const readableStream = fs.createReadStream('source.txt');
const writableStream1 = fs.createWriteStream('destination1.txt');
const writableStream2 = fs.createWriteStream('destination2.txt');
const pipe1 = readableStream.pipe(writableStream1);
const pipe2 = readableStream.pipe(writableStream2);
setTimeout(() => {
readableStream.unpipe(pipe1);
}, 1000);
~~~
>[danger] ### fs.read(fd, buffer, offset, length, position, callback)
1. 当需要从文件中读取数据时,可以使用 Node.js 的 fs 模块提供的 fs.read() 方法。该方法的作用是从指定的文件中读取数据,并将读取的数据存储到指定的 Buffer 对象中
2. 在调用` fs.read()` 方法后,`Node.js` 会尝试从指定的文件中读取数据,并将读取的数据存储到 `buffer `中。读取的数据量由 `length `参数指定,如果读取成功,则回调函数会被调用,并将读取的数据、读取的字节数和可能出现的错误传递给回调函数。
3. 在调用` fs.read`() 方法后,Node.js 会尝试从指定的文件中读取数据,并将读取的数据存储到 buffer 中。读取的数据量由 length 参数指定,如果读取成功,则回调函数会被调用,并将读取的数据、读取的字节数和可能出现的错误传递给回调函数。
>[danger] ##### api 参数
| 参数名 | 数据类型 | 描述 |
| --- | --- | --- |
| fd | Integer | 文件描述符,一个整数,代表文件在操作系统中的标识符。 |
| buffer | Buffer | 一个 Buffer 对象,用于存储读取的数据。 |
| offset | Integer | 写入数据到 buffer 中的起始位置。 |
| length | Integer | 读取的字节数。 |
| position | Integer | 文件中开始读取数据的位置。可以为 null 或 -1,表示从当前文件位置开始读取数据。 |
| callback | Function | 回调函数,用于处理读取完数据之后的操作 |
>[danger] ##### 案例
~~~
const fs = require('fs')
fs.open('b.txt', (err, fd) => {
if (err) {
console.log(err)
} else {
console.log(fd)
// 需要能直接操作fd 的api 进行操作读写
fs.read(fd, (err, data, buffer) => {
if (err) {
console.log(err)
} else {
console.log(buffer.toString(), '--')
}
// 关闭文件
// fs.close(fd)
fs.close(fd, (err) => {
if (err) {
console.error(err)
return
}
})
})
}
})
~~~
* 通过设置buffer,在 Node.js 中,Buffer 对象是用来表示二进制数据的。因为文件中的数据也是以二进制形式存储的,所以我们需要一个能够容纳二进制数据的对象来存储从文件中读取的数据。
实际上,我们可以不使用 buffer 参数,而是直接将读取到的数据存储到一个新的 Buffer 对象中,但是,如果要读取的文件非常大,那么每次读取文件时都创建一个新的 Buffer 对象会非常消耗内存。因此,如果我们已经有一个足够大的 Buffer 对象,就可以将其作为参数传递给`fs.read()`方法,这样可以避免重复分配内存,提高性能。
~~~
const fs = require('fs')
const fileSize = fs.statSync('./a.txt').size
console.log(fileSize)
const buffer = Buffer.alloc(fileSize)
const fd = fs.openSync('./a.txt')
// 读取 从文件的第20个字节开始,读取个数不超过fileSize - 20个字节
fs.read(fd, buffer, 0, fileSize - 20, 20, (err, bytesRead, buffer) => {
if (err) {
console.log(err)
}
console.log(`${bytesRead} bytes read`, buffer.toString())
fs.close(fd, (err) => {
if (err) {
console.log(err)
}
})
})
~~~
>[info] ## esm 的用法案例
~~~
import { readFile as pReadFile } from 'fs/promises'
import { readFileSync } from 'fs'
import { readFile } from 'fs'
// 1. pReadFile
pReadFile('b.txt', 'utf8').then((data) => console.log(data))
// 2. readFileSync
const data = readFileSync('b.txt', 'utf8')
console.log(data)
// 3. readFile
readFile('b.txt', 'utf8', (err, data) => {
if (err) throw err
console.log(data)
})
~~~
- 基础
- 什么是Node.js
- 理解 I/O 模型
- 理解node 中 I/O
- 对比node 和java 使用场景
- node 模块管理
- 内置模块 -- buffer
- 内置模块 -- fs
- fs -- 文件描述符
- fs -- 打开文件 api
- fs -- 文件读取 api
- fs -- 文件写入 api
- fs -- 创建目录 api
- fs -- 读取文件目录结构 api
- fs -- 文件状态(信息) api
- fs -- 删除文件/目录 api
- fs -- 重命名 api
- fs -- 复制文件 api
- 内置模块 -- events
- 内置模块 -- stream
- 可读流 -- Readable
- 可写流 -- Writable
- Duplex
- Transform
- 内置模块 -- http
- http -- 从客户端发起
- http -- 从服务端发起
- 内置模块 -- url
- 网络开发