文件的读取与写入

fs 是 file system 的简写,表示文件系统,在 Node.js 中使用 fs 模块之前,首先需要使用 require() 方法将 fs 模块引入,代码如下:

var fs = require('fs');

检查文件是否存在

fs 模块内置许多方法,用以对文件进行相关操作。具体使用时,有的方法如果发现文件不存在,可以创建文件,而有的方法则不能,这时就会出现错误,为了避免这类错误,在对文件进行操作之前,一般都需要检测文件是否存在,并且根据需要检查文件的可读或可写等属性。检查文件是否存在及其属性可以通过 access() 方法实现,语法格式如下:

fs.access(path, mode, callback)
  • path:文件的路径。

  • mode:要执行的可访问性检查,默认值为 fs.constants.F_OK。查看文件访问常量以获取可能的 mode 值,具体文件访问常量如表7.1所示。

    Table 1. 表7.1 文件访问常量
    常量 说明

    F_OK

    指示文件对调用进程可见的标志。这对于确定文件是否存在很有用,但没有说明rwx权限

    R_OK

    指示文件可以被调用进程读取的标志

    W_OK

    指示文件可以被调用进程写入的标志

    X_OK

    指示文件可以被调用进程执行的标志,在 Windows 系统中等效于 fs.constants.F_OK

  • callback:回调函数,使用一个可能的错误参数进行调用。如果检查可访问性失败,则错误参数将是 Error 对象,常见的 Error 对象值如表7.2所示。

    image 2024 04 11 19 35 25 907
    Figure 1. 表7.2 常见Error对象值及说明

fs 模块提供对文件与目录进行操作的方法时,通常分别提供了同步方法和异步方法,其中,同步方法名通常是在异步方法名后面加了 Sync 后缀,如本节中所讲的 access() 方法的对应同步方法为 accessSync(),但除了文件读写操作,一般都默认使用异步方法,所以本章讲解时,主要讲解默认的异步方法。

例如,查看 demo.txtdemo1 文件是否存在,示例代码如下:

var fs = require("fs")
//查看demo.txt文件是否存在
fs.access("demo.txt",fs.constants.F_OK, function (err) {
     if (err) {
          console.log("demo.txt文件不存在");
     }
     else {
          console.log("demo.txt文件存在");
     }
});
//查看demo1文件是否存在
fs.access("demo1 ",fs.constants.F_OK, function (err) {
     if (err) {
          console.log("demo1文件不存在");
     }
     else {
          console.log("demo1文件存在");
     }
});

程序运行结果如下:

demo.txt文件存在
demo1文件不存在

上面示例检查文件是否存在,除此之外,还可以检查文件的相关属性。如要检查文件是否可读,可以将上面代码中第 3 行的 fs.constants.F_OK 修改为 fs.constants.R_OK;而如果要检查文件是否可写,则可以将 fs.constants.F_OK 修改为 fs.constants.W_OK。另外,access() 方法的 mode 参数也可以同时设置多个值,如果 mode 参数有多个值,中间用 | 分割。例如,检查 demo.txt 文件是否存在且是否可写的代码如下:

var fs = require("fs")
//查看demo.txt文件是否存在且可写
fs.access("demo.txt",fs.constants.F_OK | fs.constants.W_OK, function (err) {
     if (err) {
          console.log(err)
          if(err.code=="ENOENT"){
               console.log("demo.txt文件不存在");
          }
          else if(err.code="EPERM"){
               console.log("demo.txt文件存在,但不可写")
          }
          else{
               console.log("未知错误")
          }
     }
     else {
          console.log("demo.txt存在,并且可写");
     }
});

在项目文件夹中放置一个设置为只读的 demo.txt 文件,然后运行上面代码,可看到运行结果如下:

[Error: EPERM: operation not permitted, access 'D:\Demo\demo.txt'] {
     errno: -4048,
     code: 'EPERM',
     syscall: 'access',
     path: 'D:\\Demo\\demo.txt'
}
demo.txt文件存在,但不可写

access() 方法不仅可以检测文件是否存在,也可以检测文件夹是否存在。

文件读取

fs 模块为读取文件提供了两个方法,即 readFile() 方法和 readFileSync() 方法,二者的区别是,前者为异步读取文件(默认操作),后者为同步读取文件,这两个方法的语法格式如下:

fs.readFile(file,encoding,callback)
fs.readFileSync(file,encoding)
  • file:文件名。

  • encoding:文件的编码格式。

  • callback:回调函数。

例如,下面代码使用 fs 模块的 readFileSync() 方法和 readFile() 方法分别对文件 poems.txtdemo.txt 进行同步和异步读取,并显示读取的内容,代码如下:

//引入模块
var fs = require('fs');
//使用readFileSync()方法同步读取文件
var text = fs.readFileSync('poems.txt', 'utf8');
console.log(text);
//使用readFile()方法异步读取文件
fs.readFile('demo.txt', 'utf8', function (error, data) {  //读取结果存储在function回调函数的第2个参数data中
     console.log(data);
});

poems.txtdemo.txt 文件内容如图7.1所示,运行上面代码后的效果如图7.2所示。

image 2024 04 11 19 43 20 881
Figure 2. 图7.1 poems.txt 和 demo.txt 文件内容
image 2024 04 11 19 43 41 355
Figure 3. 图7.2 读取结果

fs 模块中的大部分功能都可以通过同步方法和异步方法来实现,这二者的区分方法是,方法名中含有 Sync 后缀的是同步方法。同步方法和异步方法的区别是:同步方法立即返回操作结果,在使用同步方法执行的操作结束之前,不能执行后续代码;异步方法将操作结果作为回调函数的参数进行返回,在方法调用之后,可以立即执行后续代码。在大多数情况下,应该调用异步方法,但是在很少的场景中,比如读取配置文件启动服务器的操作中,应该使用同步方法。

【例7.1】模拟听歌时的显示歌词效果。(实例位置:资源包\源码\07\01)

WebStorm 中创建项目文件夹,在该项目文件夹中添加一个歌词文件 song.txt,然后创建 js.js 文件,在 js.js 文件中读取歌词文件中的内容,读取时,需要根据音乐播放进度显示对应的歌词,这需要使用正则表达式对歌词文件中的时间点进行解析(解析的格式为 [00:00.00]),以显示对应时间点的歌词内容。代码如下:

//引入模块
var fs = require('fs');
//读取歌词文件
fs.readFile('./song.txt', function(err, data) {
     if (err) {
          return console.log('歌词文件读取失败');
     }
     data = data.toString();
     var lines = data.split('\n');
     //遍历所有行,通过正则表达式匹配对应时间点(时间点的格式为[00:00.00]),并输出对应的歌词
     var reg = /\[(\d{2})\:(\d{2})\.(\d{2})\]\s*(.+)/;
     for (var i = 0; i < lines.length; i++) {
          (function(index) {
               var line = lines[index];
               var matches = reg.exec(line);
               if (matches) {
                     //获取分
                     var m = parseFloat(matches[1]);
                     //获取秒
                     var s = parseFloat(matches[2]);
                     //获取毫秒
                     var ms = parseFloat(matches[3]);
                     //获取定时器中要输出的内容
                     var content = matches[4];
                     //将分+秒+毫秒转换为毫秒
                     var time = m * 60 * 1000 + s * 1000 + ms;
                     //使用定时器,让每行内容在指定的时间输出
                     setTimeout(function() {
                          console.log(content);
                     }, time);
               }
        })(i);
     }
});

song.txt 歌词文件格式及内容如图 7.3 所示,程序运行效果如图 7.4 所示。

image 2024 04 11 19 47 51 092
Figure 4. 图7.3 歌词文件格式及内容
image 2024 04 11 19 48 12 990
Figure 5. 图7.4 模拟听歌时的显示歌词效果

本实例的 JavaScript 代码中,for 循环中所有的内容都放在了匿名函数中,并且该匿名函数需要自动执行,这样在执行该文件时,保证了每次循环都会输出一句歌词。

文件写入

文件写入时,有 4 个方法供选择,分别为 writeFile() 方法、writeFileSync() 方法、appendFile() 方法和 appendFileSync() 方法,下面分别进行介绍。

writeFile() 方法和 writeFileSync() 方法

这两个方法分别用来对文件进行异步和同步写入,它们的语法格式如下:

fs.writeFile(file, data[, options], callback)
fs.writeFileSync(file, data[, options])
  • file:文件名或文件描述符。

  • data:写入文件的内容,可以是字符串也可以是缓冲区。

  • options:可选参数,可以为以下内容。

    • encoding:编码方式,默认值为 utf8,如果 data 为缓冲区,则忽略 encoding 参数。

    • mode:文件的模式。默认值为 0o666

    • flag:文件系统标志。默认值为 w

    • signal:允许中止正在进行的写入文件操作。

  • callback:回调函数。

【例7.2】创建文件并且向文件中写入内容。(实例位置:资源包\源码\07\02)

WebStorm 中新建一个项目文件夹,在其中新建一个 poems.txt 文件,该文件中默认写入一首古诗《登鹳雀楼》;然后创建一个 js.js 文件,在 js.js 文件中首先使用 writeFile() 方法以异步方式向 poems.txt 文件中写入古诗 《春夜喜雨》,然后使用 writeFileSync() 方法以同步方式向一个本不存在的 newpeoms.txt 文件中同样写入古诗 《春夜喜雨》。代码如下:

//引入模块
var fs = require('fs');
//声明要写入的内容
var data = '       春夜喜雨\n\t\t杜甫\n好雨知时节,当春乃发生。\n随风潜入夜,润物细无声。
            \n野径云俱黑,江船火独明。\n晓看红湿处,花重锦官城。';
//使用异步方式向poems.txt文件中写入古诗
fs.writeFile('poems.txt', data, 'utf8', function (error) {
     if (error) {
          throw error;
     }
     console.log('异步写入文件完成');
});
//使用同步方式向一个本不存在的newpeoms.txt文件中同样写入古诗
fs.writeFileSync('newpoems.txt', data, 'utf8');
console.log('同步写入文件完成!');

运行程序,进入项目文件夹中,可以发现新增了 newpoems.txt 文件,如图7.5所示,这说明使用 writeFileSync 方法向文件中写入内容时,如果文件不存在,系统会自动创建。分别打开 poems.txt 文件和 newpeoms.txt 文件,发现它们的内容都是古诗《春夜喜雨》,如图7.6所示,这说明使用 writeFile 方法向文件中写入内容时,会覆盖掉原有内容。

image 2024 04 11 20 17 02 527
Figure 6. 图7.5 写入内容时自动创建的文件
image 2024 04 11 20 17 29 915
Figure 7. 图7.6 写入内容时自动覆盖原有内容

appendFile() 方法和 appendFileSync() 方法

这两个方法分别向文件异步追加内容和同步追加内容,它们的语法格式如下:

fs.appendFile(path, data[, options],callback)
fs.appendFileSync(path, data[, options])
  • path:文件路径。

  • data:要写入文件的数据。

  • callback:回调函数。

  • options:可选参数,可以为以下内容。

    • encoding:编码方式,默认值为 utf8

    • mode:文件模式,默认值为 0o666

    • flag:文件系统标志,默认值为 a

【例7.3】为古诗增加古诗鉴赏内容。(实例位置:资源包\源码\07\03)

WebStorm 中创建项目文件夹,然后向项目文件夹中添加一个 poems.txt 文件,该文件的原始内容为古诗《春夜喜雨》,然后创建 js.js 文件,其中使用 appendFile() 方法为 poems.txt 文件中的古诗添加古诗鉴赏内容。代码如下:

var fs = require("fs")
var path = "poems.txt"
var data = "\n古诗鉴赏:这首诗描写细腻、动人。诗的情节从概括的叙述到形象的描绘,由耳闻到目睹,当晚到次晨,结构谨严。用词讲究。颇为难写的夜雨景色,却写得十分耀眼突出,使人从字里行间呼吸到一股令人喜悦的春天气息。"
fs.appendFile(path, data, function (err) {
     if (err) {
          console.log(err)
     }
     else {
          console.log("内容追加完成")
    }
})

poems.txt 文件原始内容如图7.7所示,运行程序后,再次打开 poems.txt 文件,其内容如图7.8所示。

image 2024 04 11 20 21 02 806
Figure 8. 图7.7 poems.txt 文件原始内容
image 2024 04 11 20 22 27 825
Figure 9. 图7.8 追加内容后的 poems.txt 文件

上面两组方法的区别是:第一组方法向文件写入内容,将新的内容替代文件中的原有内容;第二组方法在文件原内容的后面继续追加内容。具体使用时需要根据实际情况来选择方法。

文件操作时的异常处理

前面学习了文件读取和写入的操作方法,在实际编程中,经常会出现一些异常情况。比如,读取文件时文件并不存在,或者读取文件时文件路径有误等。出现类似情况会导致程序直接崩溃。所以,无论是异步方法还是同步方法,都需要对这些异常情况进行处理。

同步操作的异常处理

使用同步方法进行文件操作时,可以使用 try-catch 语句进行异常处理。示例代码如下:

//引入模块
var fs = require('fs');
//文件读取
try {
     var data = fs.readFileSync('textfile.txt', 'utf8');
     console.log(data);
} catch (e) {
     console.log(e);
}
//文件写入
try {
     fs.writeFileSync('textfile.txt', 'Hello World .. !', 'utf8');
     console.log('完成文件写入操作');
} catch (e) {
     console.log(e);
}

异步操作的异常处理

使用异步方法进行文件操作时,可以使用 if-else 语句进行异常处理。示例代码如下:

//引入模块
var fs = require('fs');
//文件读取
fs.readFile('textfile.txt', 'utf8', function (error, data) {
     if (error) {
          console.log(error);
     } else {
          console.log(data);
     }
});
//文件写入
fs.writeFile('textfile.txt', 'Hello World .. !', 'utf8', function (error) {
     if (error) {
          console.log(error);
     } else {
          console.log('完成文件写入操作');
     }
});