NodeJS 之 fs 模块(文件操作)

NodeJS 之 fs 模块(文件系统模块)

  • 参考
  • 描述
  • fs(File System)
  • 文件
      • 读取
          • fs.readFile()
          • 判断文件是否读取成功
          • 编码
      • 写入
          • fs.writeFile()
          • 覆盖
      • 追加
          • fs.appendFile()
      • 移动
          • fs.rename()
  • 同步与异步
      • 区别
          • 后缀
          • 参数
            • 举个栗子
          • 执行
            • 同步
            • 异步
          • 错误
            • 同步
            • 异步
          • 总结
  • 更细致的文件操作
      • fs.open()
          • 可能使用到的文件操作
          • 举个栗子
      • 读取
          • fs.read()
            • 举个栗子
      • 写入
          • fs.write()
      • 关闭
      • 区别
          • fs.readFile()、fs.readFileSync()、fs.writeFile() 及 fs.writeFileSync()
          • fs.read()、fs.readSycn()、fs.write() 及 fs.writeSync()

参考

项目 描述
NodeJS NodeJS API 官方文档
Node.js 权威指南 陆凌牛
哔哩哔哩 黑马程序员

描述

项目 描述
操作系统 Windows 10 专业版
NodeJS 18.13.0

fs(File System)

fs 模块是 NodeJS 内置的文件系统模块,为开发者提供了许多用于文件及目录操作的 API。

在 NodeJS 中,我们可以通过如下代码来将该模块进行导入。

const fs = require('fs');

文件

读取

fs.readFile()
fs.readFile(path[, options], callback)

其中:

pathcallback 为必选参数,而 options 为可选参数。

参数 描述
path 需要读取的目标文件的路径。
options 指定对被读取文件中的内容使用的编码方式。
callback 用以指定文件操作执行完成后需要执行的回调函数。
判断文件是否读取成功

传递给 fs.readFile() 的回调函数可以提供两个形参来分别接收 文件读取失败后得到的错误对象文件读取成功后得到的文件内容
文件读取成功时,第一个形参将接收到的值(实参)为 null,因此可以利用这个特性来判断文件是否读取成功:

const fs = require('fs');

// 尝试读取当前工作目录下的 content.txt 文件
fs.readFile('./content.txt', (err, data) => {
    if(err){
        console.log('【文件读取失败】');
    }else{
        console.log('【文件读取成功】');
    }
})
编码

options 为可选属性,若不使用该属性指定对被读取文件中的内容使用的编码方式,则文件内容的读取结果将以 Buffer 对象的形式返回。

const fs = require('fs');

// 尝试读取当前工作目录下的 content.txt 文件
fs.readFile('./content.txt', (err, data) => {
    if(err){
        console.log('【文件读取失败】');
    }else{
    	console.log(data);
    }
})

打印结果:

你可以使用 Buffer 对象的 toString() 方法将 Buffer 对象转换为字符串:

const fs = require('fs');

// 尝试读取当前工作目录下的 content.txt 文件
fs.readFile('./content.txt', (err, data) => {
    if(err){
        console.log('【文件读取失败】');
    }else{
    	// 使用 Buffer 对象的 toString() 方法将 Buffer 对象转换为字符串
    	console.log(data.toString());
    }
})

打印结果:

隐约雷鸣,阴霾天空。但盼风雨来,能留你在此。
隐约雷鸣,阴霾天空。即使无天雨,我亦留此地。

你也可以通过 options 属性指定对被读取文件中的内容使用的编码方式来直接获取采用特定编码方式对文件内容编码后得到的字符串:

const fs = require('fs');

// 尝试读取当前工作目录下的 content.txt 文件
fs.readFile('./content.txt', 'utf-8', (err, data) => {
    if(err){
        console.log('【文件读取失败】');
    }else{
    	console.log(data);
    }
})

打印结果:

隐约雷鸣,阴霾天空。但盼风雨来,能留你在此。
隐约雷鸣,阴霾天空。即使无天雨,我亦留此地。

  1. 当提供的路径指向一个目录时,fs.readFile() 的行为是基于平台的。 在 macOS、Linux 以及 Windows 上,将得到一个错误,而在 FreeBSD 上,将返回目录内容的表示。

写入

fs.writeFile()
fs.writeFile(file, data[, options], callback)

其中:

pathdatacallback 为必选参数,而 options 为可选参数。

参数 描述
path 需要执行写入操作的目标文件的路径。
data 需要被写入文件中的内容。
options 指定将内容写入文件中时使用的编码方式,默认值为 utf8
callback 用以指定文件操作执行完成后需要执行的回调函数。

注:

传递给 fs.writeFile() 的回调函数中可以提供一个参数,用来指定接收将内容写入文件失败时产生的错误对象。

覆盖

使用 fs.writeFile() 对文件执行写入操作时,写入的内容将覆盖目标文件中的原有内容。

文件 content.txt 中的原有内容:

隐约雷鸣,阴霾天空。但盼风雨来,能留你在此。
隐约雷鸣,阴霾天空。即使无天雨,我亦留此地。

const fs = require('fs');

const content = '在我贫瘠的土地上,你是最后一朵玫瑰。';

// 尝试将变量 content 保存的内容输入到目标文本中
fs.writeFile('./content.txt', content, (err) => {
    if(err){
        console.log('【文件写入失败】');
    }else{
        console.log('【文件写入成功】')
    }
});

// 尝试读取当前工作目录下的 content.txt 文件
fs.readFile('./content.txt', 'UTF8', (err, data) => {
    if(err){
        console.log('【文件读取失败】');
    }else{
    	console.log(data);
    }
})

打印结果:

【文件写入成功】
在我贫瘠的土地上,你是最后一朵玫瑰。

追加

fs.appendFile()
fs.appendFile(path, data[, options], callback)

在参数上,fs.appendFile()fs.writeFile() 两者无异。只是在对文件进行写入操作时,fs.writeFile() 将会使用将要被写入目标文件中的内容来对目标文件中的原有内容进行覆盖,而 fs.appendFile() 则是将将要被写入目标文件中的内容附加在目标文件原有内容之后。

文件 content.txt 中的原有内容:

在我贫瘠的土地上,你是最后一朵玫瑰。

const fs = require('fs');

const content = '\n隐约雷鸣,阴霾天空。但盼风雨来,能留你在此。\n隐约雷鸣,阴霾天空。即使无天雨,我亦留此地。'

// 尝试将变量 content 中保存的内容写入到当前工作目录下的 content.txt 文件中
fs.appendFile('./content.txt', content, (err) => {
    if(err){
        console.log('【文件写入失败】')
    };
})

// 尝试读取当前工作目录下的 content.txt 文件
fs.readFile('./content.txt', (err, data) => {
    if(err){
        console.log('【文件读取失败】')
    }else{
    	console.log(data.toString());
    }
})

打印结果:

在我贫瘠的土地上,你是最后一朵玫瑰。
隐约雷鸣,阴霾天空。但盼风雨来,能留你在此。
隐约雷鸣,阴霾天空。即使无天雨,我亦留此地。

移动

fs.rename()
fs.rename(oldPath, newPath, callback)

其中:

参数 描述
oldPath 需要被移动的文件所在的路径。
newPath 需要将文件移动到的目标路径。
callback 在对文件执行某项操作后需要执行的回调函数。

注:

  1. 如果提供给 fs.rename()newPath 的值指向的文件(夹)已经存在:

    • 如果 newPath 指向的是一个文件,则该文件将被覆盖。
    • 如果 newPath 指向的是一个文件夹,程序将抛出错误。
  2. 传递给 fs.rename() 的回调函数可以提供一个形参,用以接收文件操作失败后所产生的可能的错误对象。

同步与异步

在 NodeJS 的 fs 模块,所有对文件及目录的操作都可以使用同步与异步两种方式,这两种方式分别对应 fs 对象的两种方法。

其中:

含有 Sync 后缀的方法名表示该方法将以同步的方式来处理文件或目录,
没有 Sync 后缀的方法名表示该方法将以异步的方式来处理文件或目录。

区别

后缀

有关两者在 后缀 上的区别,已在前面进行了讲述,故不在此进行赘述。

参数

在对文件或目录实施同一操作(使用同一类方法对文件或目录进行操作,该类方法中的两种方法仅存在同步与异步之分)时,同步处理与异步处理所使用的方法在参数的含义上,两者并无差异;在使用的方法所需要的参数的数量上,同步方法不需要提供事件回调函数这一实参,而异步方法则需要进行提供。

举个栗子

fs.readFile() 需要提供的参数有:

fs.readFileSync(path[, options])

而其对应的另一种实现(同步)fs.readFileSync() 所需要提供的参数有:

fs.readFileSync(path[, options])
执行
同步

同步方法立即返回操作结果,在使用同步方法执行的操作结果之前,不能执行后续代码:

const fs = require('fs');

// 使用同步方法时,你需要使用一个变量来接收被读取文件中的内容
const data = fs.readFileSync('./content.txt', 'UTF-8');
// 等待执行结果的返回,在执行结果返回后将打印文件中的内容
console.log(data);
异步

异步方法将操作结果作为回调函数的参数进行返回,在方法调用之后,可以立即执行后续代码:

const fs = require('fs');

// 尝试读取当前工作目录下的 content.txt 文件
fs.readFile('./content.txt', 'UTF8', (err, data) => {
	if(err){
		console.log('【文件读取失败】');
    }
	else{
		console.log(data);
	}
});
错误

如果使用同步方法,你需要对文件操作过程中可能发生的错误进行捕获,否则一旦出现了相关的错误,后续代码将无法顺利执行。

同步

这里,我将使用同步方法对不存在的文件进行读取:

const fs = require('fs');

// 尝试读取一个不存在的文件
const result = fs.readFileSync('./content', 'utf8');
console.log(result);

错误信息:

node:internal/fs/utils:348
    throw err;
    ^

Error: ENOENT: no such file or directory, open './content'
    at Object.openSync (node:fs:600:3)
    at Object.readFileSync (node:fs:468:35)
    at Object.<anonymous> (C:\Users\36683\Template\main.js:4:19)
    at Module._compile (node:internal/modules/cjs/loader:1218:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1272:10)
    at Module.load (node:internal/modules/cjs/loader:1081:32)
    at Module._load (node:internal/modules/cjs/loader:922:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:23:47 {
  errno: -4058,
  syscall: 'open',
  code: 'ENOENT',
  path: './content'
}

Node.js v18.13.0
异步

这里,我将使用异步方法对不存在的文件进行读取:

const fs = require('fs');

// 尝试读取一个不存在的文件
fs.readFile('./content', 'UTF8', (err, data) => {
	if(err){
		console.log('【文件读取失败】');
    }
	else{
		console.log(data);
	}
});

打印结果:

【文件读取失败】

总结

在大多数时候,你应该使用异步方法。异步方法对开发者更为友好,并且使用异步方法也更能发挥好 NodeJS 本身的优势。但也不能对同步方法一味的排斥,在某些情况下(例如 读取配置文件并启动服务器),你应该使用同步方法。

更细致的文件操作

fs.open()

使用 fs.open() 函数打开一个文件,你将能够对文件执行更细致的操作(比如从文件中的指定位置开始读写内容)。
在使用 fs.open() 函数打开一个文件后,你可以使用 fs 模块中的 fs.read() 方法或 fs.readSync() 方法从文件中的指定位置开始读取文件;你也可以使用 fs.write() 方法或 fs.writeSync() 方法从文件中的指定位置开始向文件中写入内容。
如果你需要在后续以 同步 方式对文件执行读写操作,请使用 fs.openSync() 函数将目标文件打开,该函数将返回目标文件的文件描述符,请注意接收。

fs.open(path[, flags[, mode]], callback)

其中:

pathcallback 为必选参数,而 flagmode 为可选参数。

参数 描述
path 需要被打开的目标文件的路径。
flags 指定需要执行的文件操作,默认值为 r ,也即读取操作。
mode 用于指定文件被打开时对该文件的读写权限,默认值为 0666 ,即可读可写。
callback 指定文件被打开后需要执行的回调函数。

注:

传递给 fs.open() 的回调函数可以提供两个形参以分别接收 打开文件失败所产生的可能的错误对象 以及 代表被打开的文件的文件描述符

可能使用到的文件操作
项目 描述
a 打开文件进行追加。 如果文件不存在,则创建该文件。
a+ 打开文件进行读取和追加。 如果文件不存在,则创建该文件。
r 打开文件进行读取。 如果文件不存在,程序将抛出异常。
r+ 打开文件进行读写。 如果文件不存在,程序将抛出异常。
w 打开文件进行写入。如果该文件不存在,则创建该文件。
w+ 打开文件进行读写。如果该文件不存在,则创建该文件。

在 NodeJS API 官方文档对此有更详实的描述,前往查看 你将得到更全面的内容。

举个栗子
const fs = require('fs');

// 尝试打开当前工作目录下的 content.txt 文件
fs.open('./content.txt', (err, fd) => {
    if(err){
        console.log('【文件打开失败】');
    }else{
        console.log(fd); // 打印被开启文件对应的文件描述符
    }
})

打印结果:

3

读取

fs.read()

fs.read() 函数将从文件中指定的位置开始读取文件,直到将文件读取完毕。你也可以使用 fs.readSync() 方法以同步的方式读取文件,该方法将返回实际从文件中读取到的字节数。

fs.read(fd, buffer, offset, length, position, callback)

其中:

在上述的参数中,仅 fdcallback 为必选参数。

参数 描述
fd 你需要为该参数提供一个文件描述符,该值必须为 fs.open()fs.openSync() 函数提供的文件描述符。
buffer buffer 参数值需要为一个 Buffer 对象,用于指定将文件数据读取到哪个缓冲区中。
offset 该参数用于指定将读取到的文件内容在缓冲区中开始写入的位置(以字节为单位)。
length 该参数用于指定在文件中需要读取的字节数。
position 该参数用于指定在目标文件中开始读取文件的位置(以字节为单位)。
callback 用于指定读取操作完成后需要执行的回调函数。

注:

  1. 传递给 fs.read() 方法的回调函数可以提供三个形参,分别用于接收 读取文件时可能产生的错误对象在目标文件中实际读取的字节数(指定读取的字节数可能大于文件的剩余长度) 以及 文件内容读取结果存放的缓冲区对象(Buffer)
  2. UTF-8 编码的单个中文字符所占据的大小为三个字节;单个 ASCII 字符所占据的大小为一个字节。
  3. 参数
    • 若你没有为 buffer 参数提供一个缓冲区对象,fs.read() 将为你创建一个 Buffer 对象,并允许你通过提供给 fs.read() 的回调函数中的参数来对该 Buffer 对象进行接收。
    • 若你没有指定 offset 参数的值,则默认在缓冲区对象(Buffer)的首部开始将文件内容写入到缓冲区中。
    • 若你没有指定 positionlength 的值,则将读取目标文件中的全部内容。
const fs = require('fs');

// 尝试打开当前工作目录下的 context.txt 文件
fs.open('./content.txt', 'r', (err, fd) => {
    if(err){
        console.log('【文件打开失败】');
    }else{
        fs.read(fd, (err, len, buf) => {
            if(err){
                console.log('【文件读取失败】');
            }else{
                // 打印从文件按中实际读取的内容所占据的字节数
                console.log(len);
                // 将缓冲区中的内容转换为字符串后将其打印
                console.log(buf.toString());
            }
        })
    }
})

打印结果:

190
在我贫瘠的土地上,你是最后一朵玫瑰。
隐约雷鸣,阴霾天空。但盼风雨来,能留你在此。
隐约雷鸣,阴霾天空。即使无天雨,我亦留此地。

注:
content.txt 文件中共含有 62 个字符,由于 content.txt 文件中还包含两个换行,一个换行符占用两个字节,所以在目标文件中读取的字节数为 190

举个栗子

程序运行前,content.txt 文件中的内容为:

在我贫瘠的土地上,你是最后一朵玫瑰。
隐约雷鸣,阴霾天空。但盼风雨来,能留你在此。
隐约雷鸣,阴霾天空。即使无天雨,我亦留此地。

我将尝试在读取 content.txt 文件中的第一行文本后,再次读取该文件捉弄的余下内容。

const fs = require('fs');

// 尝试打开当前工作目录下的 content.txt 文件
fs.open('./content.txt', (err, fd) => {
    if(err){
        console.log('【文件打开失败】');
    }else{
        // 创建大小为 256 字节的缓冲区对象用于
        // 存放读取的文件内容。
        const buf = new Buffer(256);

        // 第一次读取
        // 第一行中共有 18 个可见字符以及一个换行符
        // 所以此处将 length 参数的值设置为 18 * 3 + 2 = 56
        fs.read(fd, buf, 0, 56, 0, (err, len, result) => {
            if(err){
                console.log('【第一次文件读取失败】');
            }else{
                console.log('【第一次文件读取】');
                console.log(result.toString());
            }

            // 第二次文件读取
            // 第二次读取是在第一次读取完成后执行的回调函数中进行的,
            // 这样可以避免第一次与第二次打印的内容相同
            // (非阻塞机制使得两者在打印读取内容前均已将文件读取完毕)。
            fs.read(fd, buf, 56, 190, 56, (err, len, result) => {
                if(err){
                    console.log('【第二次文件读取失败】');
                }else{
                    console.log('【第二次文件读取】');
                    console.log(result.toString());
                }
            });
        });
    }
})
 

打印结果:

(node:14224) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.
(Use node --trace-deprecation … to show where the warning was created)
【第一次文件读取】
在我贫瘠的土地上,你是最后一朵玫瑰。

【第二次文件读取】
在我贫瘠的土地上,你是最后一朵玫瑰。
隐约雷鸣,阴霾天空。但盼风雨来,能留你在此。
隐约雷鸣,阴霾天空。即使无天雨,我亦留此地。

其中:

(node:14224) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.
(Use node --trace-deprecation … to show where the warning was created)

在打印结果中出现上述内容并不是我们的程序出来问题,是由于安全性和可用性问题,官方并不推荐使用 new Buffer() 来创建缓冲区对象,因此打印内容中出现了警告以表明官方推荐使用 Buffer.alloc ()Buffer.allocUnsafe()Buffer.from() 方法来代替 new Buffer() 方法。

写入

fs.write()

fs.write() 函数将从目标文件中的指定位置开始将内容写入,直到将内容完全写入到目标文件中。你也可以使用 fs.writeSync() 方法以同步的方式将内容写入文件。

fs.write(fd, buffer, offset, length, position, callback)

其中:

在上述的参数中,仅 fdcall为必选参数。

参数 描述
fd 你需要为该参数提供一个文件描述符,该值必须为 fs.open()fs.openSync() 函数提供的文件描述符。
buffer buffer 参数值需要为一个 Buffer 对象,用于指定需要写入目标文件中的内容从哪一个缓冲区对象中读取。
offset 该参数用于指定从缓冲区中开始读取内容的位置(以字节为单位)。
length 该参数用于指定在缓冲区中需要读取的字节数。
position 该参数用于指定在目标文件中开始写入内容的位置(以字节为单位)。
callback 用于指定写入操作完成后需要执行的回调函数。

关闭

在 NodeJS 中的 fs 模块中,存在两个方法 fs.close()fs.closeSync() 用于关闭由 fs.open()fs.openSync() 打开的文件。

fs.close(fd, [callback])

其中:

fd 为必选参数。

项目 描述
fd 需要被关闭的文件的文件描述符。
callback 在关闭文件这一操作完成后需要执行的回调函数。

注:

你可以为传递给 fs.close() 函数的回调函数提供一个形参用以接收关闭文件过程中可能产生的错误对象。

区别

fs.readFile()、fs.readFileSync()、fs.writeFile() 及 fs.writeFileSync()

在使用 fs.read()Filefs.readFileSync()fs.writeFile()fs.writeFileSync() 等函数对文件进行读写操作时,NodeJS 首先将内容完整读写到缓存当中,再将该缓存中的内容读取或将该内容写入目标文件中。

也就是说:

在使用上述方法时,NodeJS 将内容视为一个整体,为其分配缓冲区并且一次性将内容读取到缓冲区中后,一次性将内容进行读取或写入目标文件中。

fs.read()、fs.readSycn()、fs.write() 及 fs.writeSync()

fs.read()Filefs.readFileSync()fs.writeFile()fs.writeFileSync() 等方法不同的是,fs.read()fs.readSycn()fs.write()fs.writeSync() 并不将内容看成一个整体,而是将内容看成许多个部分。

在对文件进行处理时,fs.read()fs.readSycn()fs.write()fs.writeSync() 方法会将内容分为许多个部分,重复的对这些部分进行读取或写入。

至于为什么要这样设计,暂不清楚。

你可能感兴趣的:(后端,JavaScript,javascript,后端,文件操作,fs,NodeJS)