Node入門教程(9)第七章:NodeJs的檔案處理

weixin_33941350發表於2018-04-12

Node的檔案處理涉及到前面說的ptah模組,以及fs檔案系統、stream流處理、Buffer緩衝器等模組。內容可能比較多,相關內容請以官網文件為主,此處主要以案例講解為主,分享給大家一些常用的經典案例。細節就不展開了。

fs檔案系統

fs模組提供了很多檔案操作相關的api,比如:監控資料夾、檔案,檔案重新命名,檔案讀寫,檔案修改許可權、檔案讀寫流等。

在此,我們僅以幾個案例的方式來驅動學習Node的檔案系統,細節請詳細閱讀Node的api文件或者原始碼。

案例:

  • 如何監控資料夾的變化?

  • 如何讀取一個檔案?

  • 如何把內容寫入另外一個檔案?

  • 檔案件讀取、檔案重新命名、移動等各種功能

如何監控資料夾的變化?

fs模組提供了FSWatcher類輔助我們進行監控資料夾,可以通過fs.watch()方法返回此型別例項。然後通過註冊相關的事件回撥函式達到對檔案變化的監控。不廢話,直接上程式碼,詳細內容請參考官方文件

// 引入fs模組
const fs = require('fs');

// 通過fs.watch方法可以建立一個fs.FSWatcher類的例項。
let watcher = fs.watch(
  __dirname,                  // 監控的資料夾,我這裡用了一個模組的變數,當前js檔案所在的目錄
  { recursive: true },        // 是否監控子資料夾,還可以設定編碼,具體參考官網文件
  (eventName, fileName) => {  // 回撥函式接受兩個引數:事件名字和檔名
    console.log(`事件名字:${eventName}, 檔名字: ${fileName}`);
  }
);

// 還可以單獨註冊事件,回撥函式跟watch方法一致。還可以監聽:error事件。
watcher.on('change', (eventType, fileName) => {
  console.log('事件名:%s , 檔名: %s', eventType, fileName);
});

// 設定13秒中後,退出監控資料夾
setTimeout(() => {
  // 關閉監控。
  watcher.close(function(err) {
    if (err) {
      console.error(err);
    }
    console.log('關閉watch');
  });
}, 13000);

/********
以下是輸出的結果:當js檔案修改的時候
事件名字:change, 檔名字: 05filewatch.js
事件名:change , 檔名: 05filewatch.js
事件名字:change, 檔名字: 05filewatch.js
事件名:change , 檔名: 05filewatch.js
*/

如何讀取一個檔案?

讀取檔案可以分為小檔案讀取和大檔案,如果小檔案可以一次性的把檔案內容讀取出來然後再處理,對於比較大的檔案用檔案流處理。

一次性讀取小檔案的方法

fs.readFile()方法可以幫助我們一次性的把檔案裡面內容讀取出來。文件

const fs = require('fs');
const path = require('path');

// 把當前js所在的目錄中的a.html檔案的路徑賦值給 fileName
let fileName = path.join(__dirname, 'a.html');

// 讀取a.html檔案,按照utf8的編碼方式讀取。回撥函式第一個引數是err(這個是一個預設的約定規範,大多數node
// 的回撥函式第一個引數都是異常的err,如果為空則表示沒有錯誤。)第二個引數是檔案的所有內容。
fs.readFile(fileName, { encoding: 'utf8' }, (err, data) => {
  if (err) throw err; // 判斷是否讀取錯誤
  console.log(data);  // 檔案內容讀取並列印到控制檯。
});

//readFileSync方法是readFile的同步版本,沒有回撥函式,函式的返回值就是檔案內容。
// 以下程式碼是同步讀取,不使用回撥函式,此方法不是用的: libuv 的執行緒池的執行緒執行,所以慎用!!
let fileContent = fs.readFileSync(fileName, { encoding: 'utf8' });
console.log(fileContent);

注意:此方法只適合比較小的檔案,不適合大檔案讀取。
同步方法儘量少用,非同步的讀取檔案都是利用了libuv 的執行緒池的執行緒讀取檔案,所以讀取檔案等待期間不會阻塞主執行緒的事件迴圈。

讀取大檔案

使用stream讀取大檔案。當然你可以自定義可讀流,也可以用node內建的建立可讀流的api。

const fs = require('fs');
const path = require('path');

// 把當前js所在的目錄中的a.html檔案的路徑賦值給 fileName
let fileName = path.join(__dirname, 'a.html');

// 建立可讀流
let readStream = fs.createReadStream(fileName, {
  flags: 'r',       // 設定檔案只讀模式開啟檔案
  encoding: 'utf8'  // 設定讀取檔案的內容的編碼
});

// 開啟檔案流的事件。
readStream.on('open', fd => {
  console.log('檔案可讀流已開啟,控制程式碼:%s', fd);
});

// 可讀流開啟後,會源源不斷的觸發此事件方法,回撥函式引數就是讀取的資料。
readStream.on('data', data => {
  console.log(data);
});

readStream.on('close', () => {
  console.log('檔案可讀流結束!');
});

如何寫入檔案

寫入檔案也是一樣的,如果是比較少的內容可以一次性寫入檔案。其他情況可以用流、管道等方式解決。

一次性寫入少量內容到檔案

const fs = require('fs');
const path = require('path');

// 把當前js所在的目錄中的a.html檔案的路徑賦值給 fileName
let fileName = path.join(__dirname, 'a.html');

// 寫入檔案
fs.writeFile(
  fileName,                                            // 檔名    
  '<h1>aicoder.com 全棧實習不8000就業不還實習費</h1>',     // 寫入檔案內容
  err => {                                             // 寫入成功後的回撥函式
    if (err) throw err;
    console.log('檔案內容已經寫入!');
  }
);

寫入大量資料到檔案

寫入大量檔案的方式,可以用流的方式,可以用管道的方式,使用基本類似。且看程式碼。

  • 流的方式寫入檔案。

語法: fs.createWriteStream(path, [options])
引數:

  • path 檔案路徑
  • [options] flags:指定檔案操作,預設'w',;encoding,指定讀取流編碼;start指定寫入檔案的位置
const fs = require('fs');
const path = require('path');

// 把當前js所在的目錄中的a.html檔案的路徑賦值給 fileName
let fileName = path.join(__dirname, 'msg.log');

// 建立議檔案的可寫流。
let ws = fs.createWriteStream(fileName, { start: 0 });

ws.on('open', function(fd) {
  console.log('要寫入的資料檔案已經開啟,檔案描述符是: ' + fd);
});

// 監聽寫入異常事件
ws.on('error', err => {
  console.log(err);
});

// 監聽寫入完成的事件
ws.on('finish', () => {
  console.log('寫入結束!');
});

// 寫入100個字串。
for (let i = 0; i < 100; i++) {
  // write方法可以把內容寫入到可寫流中。
  let w_flag = ws.write('aicoder.com  全棧實習 \r\n');
  //當快取區寫滿時,輸出false
  console.log(w_flag);
}

ws.end('結束寫入!');
  • 大檔案的複製(綜合運用檔案流)

大檔案複製,可以用一個可讀流和一個可寫流進行復制,由於讀取檔案一般比寫入檔案要快。所以要避免緩衝區不可寫入的時候,暫停檔案流的讀取,可以繼續寫入的時候,再繼續讀取資料。

const fs = require('fs');
const path = require('path');

let fileNameSrc = path.join(__dirname, 'jdk.dmg');      // 複製的原始檔
let fileNameDist = path.join(__dirname, 'jdk1.dmg');    // 拷貝完的目標檔名

// 建立可讀流
let rs = fs.createReadStream(fileNameSrc, { start: 0 });
// 建立可寫流
let ws = fs.createWriteStream(fileNameDist, { start: 0 });

rs.on('data', function(chunk) {
  if (ws.write(chunk) === false) {
    // 判斷資料流是否已經寫入目標了
    rs.pause();
  }
});

// 當可讀流結束的時候,讓可寫流結束。
rs.on('end', function() {
  ws.end(); // 結束可寫流
  console.log('檔案複製成功');
});

ws.on('drain', function() {
  rs.resume(); // 繼續啟動讀取資料流
});
  • 使用管道的方式大檔案的複製。
const fs = require('fs');
const path = require('path');

let fileNameSrc = path.join(__dirname, 'jdk.dmg');      // 複製的原始檔
let fileNameDist = path.join(__dirname, 'jdk1.dmg');    // 拷貝完的目標檔名  

let rs = fs.createReadStream(fileNameSrc, { start: 0 });
let ws = fs.createWriteStream(fileNameDist, { start: 0 });

rs.on('end', () => {
  console.log('讀取完畢');
});
ws.on('finish', () => {
  console.log('寫入成功!');
});

// 可讀流直接跟可寫流建立管道。可讀流的資料 直接通過管道流向 可寫流
rs.pipe(ws);
/*******************

|----------|
|----------|
|---rs  ---|-----------\
|----------|           |
|__________|           |
                       |
                    |  |       |
                    |  |       |
                    |----------|
                    |  ws      |
                    |__________|

********************/

檔案相關的其他操作

重新命名

語法:fs.rename(oldPath, newPath, callback);
引數:

  • oldPath, 原目錄/檔案的完整路徑及名;
  • newPath, 新目錄/檔案的完整路徑及名;如果新路徑與原路徑相同,而只檔名不同,則是重新命名
  • [callback(err)], 操作完成回撥函式;err操作失敗物件
fs.rename(__dirname + '/test', __dirname + '/fsDir', function (err) {
  if(err) {
    console.error(err);
    return;
  }
  console.log('重新命名成功')
});

修改檔案或目錄的操作許可權

語法:fs.utimes(path, mode, callback);
引數:

  • path, 要檢視目錄/檔案的完整路徑及名;
  • mode, 指定許可權,如:0666 8進位制,許可權:所有使用者可讀、寫,
  • [callback(err)], 操作完成回撥函式;err操作失敗物件
fs.chmod(__dirname + '/fsDir', 0666, function (err) {
  if(err) {
    console.error(err);
    return;
  }
  console.log('修改許可權成功')
});

檢視檔案與目錄的是否存在

語法:fs.exists(path, callback);
引數:

  • path, 要檢視目錄/檔案的完整路徑及名;
  • [callback(exists)], 操作完成回撥函式;exists true存在,false表示不存在
fs.exists(__dirname + '/te', function (exists) {
  var retTxt = exists ? retTxt = '檔案存在' : '檔案不存在';
  console.log(retTxt);
});

資料夾讀取操作

const fs = require('fs');
const path = require('path');

// 讀取資料夾
fs.readdir(__dirname, function(err, files) {
  if (err) {
    console.error(err);
    return;
  }
  // 對資料夾中的所有路徑做處理
  files.forEach(file => {
    let filePath = path.join(__dirname, file);
    // 讀取檔案的資訊,判斷是檔案還是路徑
    fs.stat(filePath, function(err, stat) {
      if (err) throw err;
      console.log(filePath, stat.isFile() ? ' is: file' : ' is: dir');
    });
  });
});

後續的學習

後續,可以實現一下自定義的可寫流、可讀流,多運用一些管道的方法,多看一下官方的文件,相信您已經可以掌握了檔案模組相關的內容。


參考:

  1. node.js之fs模組
  2. Node.js 檔案系統fs模組

老馬免費視訊教程

返回教程列表首頁

github地址:https://github.com/malun666/aicoder_node

 

相關文章