1
0
Fork 0
Obsidian 管理的个人笔记仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

4.4 KiB

这篇文章是 Working with the file system on Node.js学习过程中的个人总结和笔记,如果你对 Nodejs 中的文件系统感兴趣,我更建议你直接去看原文章。

Node.js 文件系统 APIs 具有多种不同的风格

  • 同步风格的函数调用 fs.readFileSync(path, options?): string | Buffer
  • 异步风格的函数调用
    • 异步回调 fs.readFile(path, options?, callback): void
    • 异步 Promise fsPromises.readFile(path, options?): Promise<string|Buffer>

它们在名称上很容易区分:

  • 异步回调的函数名,如 fs.readFile()
  • 异步 Promise 的函数名和异步回调的函数名相同,但是在不同的模块中,如 fsPromises.readFile()
  • 对于同步的函数名,则是在函数结尾加上 Sync 字样,如 fs.readFileSync

访问文件的方式

  1. 可以通过字符串的方式读写文件
  2. 可以打开读取流或写入流,并将文件分成更小的块,一次一个。流只允许顺序访问
  3. 可以使用文件描述符或文件句柄,通过一个类似流的 API 获得顺序或是随机访问
    • 文件描述符 (File descriptors) 是一个用于表示文件的整数,Nodejs 中有很多函数可以管理文件描述符
  4. 只有同步风格和异步回调风格的 API 使用文件描述符,对于异步 Promise 风格的 API,则有着更加抽象的类实现:文件句柄类 (class FileHandle),它基于文件描述符

文件系统中的一些重要类 (Important classes)

URLs:字符串文件系统路径的替代方案

当 Nodejs 的一个文件系统 API 支持字符串形式的路径参数,那么它通常也会支持 URL 的实例

fs.readFileSync('/tmp/text-file.txt')
fs.readFileSync(new URL('file:///tmp/text-file.txt'))

手动转换文件路径看起来很容易,但是实际上隐藏着许多坑,所以更加推荐以下函数对路径进行转换:

  • url.pathToFileURL()
  • url.fileURLToPath()

Buffers:在 Nodejs 中表示固定长度的字节序列,它是 Uint8Array 的子类,更多的在二进制文件中使用

由于 Buffer 实际上是 Uint8Array 的子类,和 URL 与 String 的关系相同,当 Nodejs 接受一个 Buffer 的同时,它也接受 Uint8Array,考虑到 Uint8Array 具有跨平台(cross-platform) 的特性,很多时候可能会更加合适

Buffers 可以做一件 Uint8Array 不能做到的事:以各种编码方式对文本进行编码和解码,如果我们需要在 Uint8Array 中编码或解码 UTF-8,我们可以使用 class TextEncoder 或是 class TextDecoder

文件系统中的文件读和写

读取文件并将其拆分为行

这里将会用两个读取文件并按行拆分的例子演示 fs.readFile 和 stream 读取的差异

fs.readFile

import * as fs from 'node:fs';
fs.readFileSync('text-file.txt', {encoding: 'utf-8'})

这种方法的优缺点:

  • 优点:使用简单,在大多数情况下可以满足需求
  • 缺点:对大文件而言并不是一个好的选择,因为它需要完整的读取完数据

按行拆分

// 拆分不包含换行符
const RE_SPLIT_EOL = /\r?\n/;
function splitLines(str) {
	return str.split(RE_SPLIT_EOL);
}
// ['there', 'are', 'multiple', 'lines']
splitLines('there\r\nare\nmultiple\nlines');

// 拆分包含换行符
const RE_SPLIT_AFTER_EOL = /(?<=\r?\n)/;
function splitLinesWithEols(str) {
	return str.split(RE_SPLIT_AFTER_EOL);
}
// ['there\r\n', 'are\n', 'multiple\n', 'lines']
splitLinesWithEols('there\r\nare\nmultiple\nlines')

stream

原文章中使用的是跨平台的 web stream,且声明了两个 class (ChunksToLinesStream、ChunksToLinesTransformer),类的代码太长这里就不贴出来了,这两个类也是实现 web stream 逐行读取的关键。

import * as fs from "node:fs";
import { Readable } from "node:stream";

const nodeReadable = fs.createReadStream(
	"text-file.txt",
	{ encoding: 'utf-8' }
);
const webReadableStream = Readable.toWeb(nodeReadable);
// 逐行读取
// const lineStream = webReadableStream.pipeThrough(
// 	new ChunksToLinesStream();
// )
for await (const line of lineStream) {
	console.log(line);
}

web stream 是异步可迭代的,这也是为什么上述代码中使用 for-await-of

如果我们并不需要对内容逐行读取,那么我们就不需要用到 ChunksToLinesStream