/ node

Node.js中的文件系统-fs模块

标签:node javaScript

在任何的系统中,与文件的交互都是非常重要的,特别是当配置参数写在一个文件中时,对文件系统的操作是必不可少的。在Node.js中同样也有专用的模块fs来处理与文件系统的交互。fs模块中,提供了与文件系统进行交互的良好的接口。就可以方便的对文件进行打开、读取、写人等操作。


同步与异步使用文件系统的差异

Node.js中的fs模块几乎所有的功能都会有两种形式可供选择:异步和同步。如读取文件的同步形式为readFileSync(path,[option],callback)和异步模式readFile(path,[option])。所以在编写代码时,理解这两种形式的不同是非常重要的。

  • 同步方式的文件系统调用会导致阻塞,由于Node.js是单线程的,直到文件调用完成后,控制权才会被放回主线程,这也就导致了后台其他线程池中的很多线程不能够执行,从而导致Node.js中一定程度的性能问题。因此应该尽可能的减少使用同步方式的文件系统的方法调用。
  • 异步调用会被主线程放置在事件队列中以备随后运行,这使得调用能够融入Node.js中的事件模型。但在执行代码时,就会变得有点棘手,因为事件并不能在预期的时候完成,这就需要有一个callback函数来规定当调用完成时需要完成的事。(这里可以去深入了解下Node.js的事件队列)

当然在大多数情况下,同步和异步文件系统调用的底层功能是一样的,只是实现的方式有区别。同步和异步文件系统调用都接受相同的参数,但是异步的方式都需要有一个额外的参数,即在调用完成时需要执行的回调函数。

总结一下:同步调用立刻执行,当执行完成后才会返回主线程进行对时间队列中事件的调用,而异步则是将该事件放入到事件队列中,实际的调用则是发生在它被事件循环提取时。


打开/关闭文件

一旦文件被打开,就可以读取文件中的内容或是往文件中写入文件,可以通过一个特定参数来设置。主要的方法为:

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

参数列表:

  • path: 用于指定文件系统的标准路径字符串。
  • flags: 用于指定打开文件的模式,读、写、追加等。
  • mode:用于设置文件访问模式,默认为0666,这表示可读可写。

|模式 | 说明|
|=|=|
|r | 打开文件用于读取,若文件不存在,则抛出异常|
|w | 打开文件用于写操作,若文件不存在,则创建;若文件存在,则截断该文件(清空)|
|wx | 同w,但是若文件存在,则打开失败|
|a | 打开文件用于追加,若不存在,则创建文件|
|ax | 同a,但若不存在,则打开失败|
|*+ | 对于r w wx来说使得操作变为读写同时存在,对于a来说增加读取操作|

文件开启后,若要关闭它迫使操作系统把更改应用到磁盘上,并释放操作系统锁,就需要调用以下方法来关闭一个打开的文件:

  • fs.close(fb,callback);
  • fs.closeSync(fb);

fb参数为一个文件的描述符,同步打开方式会将该描述符返回,而异步方式会放在回掉函数中:

//同步方式
var fd = fs.openSync('a.txt','w');
fs.closeSync(fd);
//异步方式
fs.open('a.txt','w',function(err,fd){
    if(!err){
        fs.close(fd);
    }
})

文件的写入

文件的写入主要分成两种:简单文件写入和非简单写入,简单写入方式把一个字符串或是缓冲区(Buffer)的内容全部直接写入到一个文件中,而非简单写入方式可以指定数据的长度、起始位置等内容或是以流式方式写入。

简单文件写入

这是将一段数据写入文件的最简单方式。分为同步和异步两种形式:

  • fs.writeFile(path,data,[options],callback)
  • fs.writeFileSync(path,data,[options])

参数列表:

  1. path:指定文件路径,可以是相对或是绝对路径。
  2. data: 指定将要被写入到文件中的StringBuffer对象。
  3. options:可以包含定义字符串编码,以及打开文件是使用的模式和标志的encodingmodeflag属性。
  4. callback:为异步方式专用。

简单实现的小例子

var fs = require('fs');
var config = {
  maxFiles: 20,
  maxConnections: 15,
  rootPath: "/webroot"
};
var configTxt = JSON.stringify(config);
var options = {encoding:'utf8', flag:'w'};
fs.writeFile('../data/config.txt', configTxt, options, function(err){
  if (err){
    console.log("Config Write Failed.");
  } else {
    console.log("Config Saved.");
  }
});

//控制台输出
Config Saved.

非简单文件写入

非简单文件写入也分两种:面对String/Buffer(缓冲区的)和面对流的文件写入方式。

面对String/Buffer的写入方式同样也分同步和异步两种方式:

  • fs.writeSync(fd,data,offset,length,position);
  • fs.write(fd,data,offset,length,position,callback);

参数列表

  1. fd:为打开文件返回或是在callback参数中的文件描述符;
  2. data:为将被写入到文件中的String/Buffer对象;
  3. offset:指定data参数中开始索引的位置,如果想从当前索引开始,这个值就为null
  4. length:指定要写入的字节数,指定为null时表示一直写到末尾;
  5. position:指定文件中的写入位置,指定为null时表示当前位置;
  6. callback:只在异步中存在,可以接受两个参数errorbytesbytes表示写入的字节数;

以下为同步写入文件:

var fs = require('fs');
var veggieTray = ['carrots', 'celery', 'olives'];
fd = fs.openSync('../data/veggie.txt', 'w');
while (veggieTray.length){
  veggie = veggieTray.pop() + " ";
  var bytes = fs.writeSync(fd, veggie, null, null);
  console.log("Wrote %s %dbytes", veggie, bytes);
}
fs.closeSync(fd);

//控制台显示
Wrote olives  7bytes
Wrote celery  7bytes
Wrote carrots  8bytes

异步写入方式:

var fs = require('fs');
var fruitBowl = ['apple', 'orange', 'banana', 'grapes'];
function writeFruit(fd){
  if (fruitBowl.length){
    var fruit = fruitBowl.pop() + " ";
    fs.write(fd, fruit, null, null, function(err, bytes){
      if (err){
        console.log("File Write Failed.");
      } else {
        console.log("Wrote: %s %dbytes", fruit, bytes);
        writeFruit(fd);
      }
    });
  } else {
    fs.close(fd);
  }
}
fs.open('../data/fruit.txt', 'w', function(err, fd){
  writeFruit(fd);
});

//控制台显示
Wrote: grapes  7bytes
Wrote: banana  7bytes
Wrote: orange  7bytes
Wrote: apple  6bytes

面对流的文件写入

往一个文件中写入大量数据,最好的方法就是使用流,把文件作为一个Writable打开,就可以往里面写数据,或是使用pipe()方法,将一个Readable流链接到Writable上,这样就很容易写来自源Readable流(如HTTP请求)的数据了;

使用以下方法创建一个Writable流:

  • fs.createWriteStream(path,[options]);

参数列表:

  1. path:指定文件的路径,可以是相对或是绝对路径;
  2. options:定义字符串编码以及打开文件时使用的模式和标志的encodingmode、和flag属性

一旦打开了Writable文件流,就可以使用标准的流式write(buffer)方法来使用,写入完成后,再调用end();

一个简单的小例子

var fs = require('fs');
var grains = ['wheat', 'rice', 'oats'];
var options = { encoding: 'utf8', flag: 'w' };
var fileWriteStream = fs.createWriteStream("../data/grains.txt",  options);
fileWriteStream.on("close", function(){
  console.log("File Closed.");
});
while (grains.length){
  var data = grains.pop() + " ";
  fileWriteStream.write(data);
  console.log("Wrote: %s", data);
}
fileWriteStream.end();

//控制台输出
Wrote: oats
Wrote: rice
Wrote: wheat
File Closed.

文件读取

涉及的方法,以及方式都和文件的写入差不多,主要的方法有:

简单读取

  • fs.readFile(path,[options],callback);
  • fs.readFildSync(path,[options]);

非简单读取

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

流式读取

  • fs.createReadStream(path,[options]);

fs模块的其他方法

验证文件/目录路径的存在性

  • fs.exists(path,callback);
  • fs.existsSync(path);

注: 同步方法返回true/false,异步方法callback仅仅只有一个err对象表示是否删除成功。


获取文件信息

  • fs.stats(path,callback);
  • fs.statsSync(path);

注: 方法返回或在回掉中放置一个Status对象,该对象下有一些列的方法和属性如下:

|属性/方法 | 说明|
|=|=|
|isFile() | 如果是一个文件,则返回true|
|isDirectory() | 如果是一个目录,则返回true|
|isSocket() | 如果是一个套接字,则返回true|
|dev | 文件所在设备的ID|
|mode | 文件的访问模式|
|size | 文件的字节数|
|blksize | 存储该文件的块大小,以字节为单位|
|blocks | 文件在磁盘上占用的块的数目|
|atime | 上次访问文件的时间|
|mtime | 文件的最后修改时间|
|ctime | 文件的创建时间|

简单的小例子:

var fs = require('fs');
fs.stat('file_stats.js', function (err, stats) {
  if (!err){
    console.log('stats: ' + JSON.stringify(stats, null, '  '));
    console.log(stats.isFile() ? "Is a File" : "Is not a File");
    console.log(stats.isDirectory() ? "Is a Folder" : "Is not a Folder");
    console.log(stats.isSocket() ? "Is a Socket" : "Is not a Socket");
    stats.isDirectory();
    stats.isBlockDevice();
    stats.isCharacterDevice();
    //stats.isSymbolicLink(); //only lstat
    stats.isFIFO();
    stats.isSocket();
  }
});

//控制台
stats: {
  "dev": -1065463736,
  "mode": 33206,
  "nlink": 1,
  "uid": 0,
  "gid": 0,
  "rdev": 0,
  "ino": 1125899906898288,
  "size": 535,
  "atime": "2016-07-07T04:03:18.481Z",
  "mtime": "2014-11-10T22:12:29.000Z",
  "ctime": "2016-07-07T04:03:18.504Z",
  "birthtime": "2016-07-07T04:03:18.481Z"
}
Is a File
Is not a Folder
Is not a Socket


列出文件

  • fs.readdir(path,callback);
  • fs.readdirSync(path);

注: 方法返回或在回掉中放置一个在指定路径条目名字的字符串数组

一个深度遍历文件夹内容的例子:

var fs = require('fs');
var Path = require('path');
function WalkDirs(dirPath){
  console.log(dirPath);
  fs.readdir(dirPath, function(err, entries){
    for (var idx in entries){
      var fullPath = Path.join(dirPath, entries[idx]);
      (function(fullPath){
        fs.stat(fullPath, function (err, stats){
          if (stats && stats.isFile()){
            console.log(fullPath);
          } else if (stats && stats.isDirectory()){
            WalkDirs(fullPath);
          }
        });
      })(fullPath);
    }
  });
}
WalkDirs("../admin"); 

删除文件

  • fs.unlink(path,callback);
  • fs.unlinkSync(path);

注: 同步方法返回true/false,异步方法callback仅仅只有一个err对象表示是否删除成功。


建立/删除目录

  • fs.mkdir(path,[mode],callback);
  • fs.mkdirSync(path,[mode]);
  • fs.rmdir(path,[mode],callback);
  • fs.rmdirSync(path,[mode]);

注: 同步方法返回true/false,异步方法callback仅仅只有一个err对象表示是否建立/删除成功。无论哪个方法都只能删除/建立一级目录。


重命名文件/目录

  • fs.rename(oldPath,newPath,callback);
  • fs.renameSync(oldPath,newPath);

注: 同步方法返回true/false,异步方法callback仅仅只有一个err对象表示是否重命名成功。


截断(清空)文件

截断文件是指通过把文件结束处设置为比当前值小赖减小文件大小。对于文件两不断增长的文件使用截断就会有良好的效果。

  • fs.truncate(path,len,callback);
  • fs.truncateSync(path,len);

注: 同步方法返回true/false,异步方法callback仅仅只有一个err对象表示是否截断成功。


监视文件更改

如果你希望在文件发生改动时,做一些事情。这个方法就是为了应对这种场景。

  • fs.watchFile(path,[options],callback);

options参数包含persistent(持续)和interval属性。如果文件被监视,就运行这个程序,persistent就为trueinterval属性指定文件更改触发的间隔时间。callback会附带两个Status对象,分别表示现在和之前的状态。

每隔5秒监视一个名为log.txt的文件,并使用Status对象来输出本次和上次被修改的时间。

fs.watchFile('log.txt',{persistent:true,interval:5000},function(curr,prev){
    console.log('log.txt modified at: ' + curr.mtime);
    console.log('Previous modification was: ' + prev.mtime);
})

小结:Node.js中的fs模块差不多就这些内容了,主要实现了与文件系统交互的能力,使得Node.js更好的使用文件。