在Node.js中如何实现异步文件处理?

  本文演示如何使用Node.js和原生Node模块在处理文件等I/O操作中的最佳实践。主要涉及Promise等异步操作语法,文件处理分读操作和写操作两个部分:

读操作

读操作分为读取小文件和大文件两种办法:

首先读取大部分小文件的操作:

  • 所有文件读操作都是读取到内存中
  • 最大文件大小不能超过2g,任何超过此大小会抛出错误:
    is greater than possible Buffer: 0x7fffffff bytes at FSReqWrap.readFileAfterStat [as oncomplete] (fs.js:386:11)
  • 代码如下:
// imports at top
 var fs = require('fs');
// Modern approach
function readFile(fileName, options) {
    return new Promise(function(resolve, reject) {
      var cb = function cb(err, data) {
          if (err) {
              return reject(err);
          }
          resolve(data);
      };
      fs.readFile.call(fs, fileName, options, cb);
    });
}      
// example:
readFile(__dirname + "/file.txt").then(function(data) {
   // data = the whole file
     console.log(data.length);
}).catch(function(err) {
  console.log(err);
}) 

 

其次,如果是读取更大的文件:

  • 使用下述方法可以读取任何大小的文件,因为它能实时变成字节流。

  • 我们设置highWaterMark 为我们希望设置的Buffer大小,一般是512k大小,对于内存存储比较有效。

  • 回调会有大量一次性512Kb大小的数据。增加更多字节不会对性能有任何帮助。
  • 下面案例演示在三分钟内(212秒)读取7gb大小文件(https://dumps.wikimedia.org/other/wikibase/wikidatawiki/ - latest-all.json.gz)
  • 注意: 上述案例的数据是被压缩gzipped ,不能被转为可读格式,为了能解压和流化可读数据可参考下面代码
// imports at top  
var fs = require('fs');
// Modern approach
function readFileStream(fileName, cb, options) {
      return new Promise(function(resolve, reject) {
          var readable = fs.createReadStream(fileName, options);
          readable.on('data', function(data) {
              cb(data);
          })
          readable.on('end', function() {
              resolve();
          })
          readable.on('error', function(err) {
              return reject(err);
          })
      })
}
 
// example:
function printDataAsItComesIn(data) {
  // Data will be null when it reaches the end.
  if (!data) return;
  // type of data = Buffer
  var stringData = data.toString('utf8', 0, data.length);
  console.log(stringData.length);
}
readFileStream(__dirname + "/latest-all.json.gz", printDataAsItComesIn, {highWaterMark: 512 * 1024}).then(function() {
  console.log("\nDone reading");
}).catch(function(err) {
  console.log(err);
}) 

 

  • 读取和解压大型文件(7gb file fize)
// imports at top
var fs = require('fs');
var zlib = require('zlib'); 
function unzipFileStream(fileName, cb, options) {
  return new Promise(function(resolve, reject) {
      var readable = fs.createReadStream(fileName, options);
      var unzip = zlib.createGunzip({chunkSize: 512 * 1024})
      readable.pipe(unzip)
      unzip.on('data', function(data) {
          cb(data);
      })
      unzip.on('end', function() {
          resolve();
      })
      unzip.on('error', function(err) {
          return reject(err);
      })
  })
}
 
// callback that lets us transform the data as it comes in
function printDataAsItComesIn(data) {
  if (!data) return;
  // data = Buffer
  // here we can actually read the data in the zip file
  var stringData = data.toString('utf8', 0, data.length);
  console.log(stringData);
}
 
// example:
unzipFileStream(__dirname + "/latest-all.json.gzip", printDataAsItComesIn, {highWaterMark: 512 * 1024}).then(function() {
  console.log("\nDone reading");
}).catch(function(err) {
  console.log(err);
}) 

 

Node.JS专题