每天學點node系列-fs檔案系統

一半水一半冰發表於2019-07-09

好的程式碼像粥一樣,都是用時間熬出來的。

概述

檔案 I/O 是由簡單封裝的標準 POSIX 函式提供的。 通過 require('fs') 使用該模組。

所有檔案系統操作都具有同步和非同步的形式。

非同步的形式總是將完成回撥作為其最後一個引數。 傳給完成回撥的引數取決於具體方法,但第一個引數始終預留用於異常。 如果操作成功完成,則第一個引數將為 null 或 undefined

// 非同步示例
const fs = require('fs');

fs.unlink('/tmp/hello', (err) => {
  if (err) throw err;
  console.log('已成功刪除 /tmp/hello');
});

使用同步的操作發生的異常會立即丟擲,可以使用 try/catch 處理,也可以允許冒泡。

//同步示例
const fs = require('fs');

try {
  fs.unlinkSync('/tmp/hello');
  console.log('已成功刪除 /tmp/hello');
} catch (err) {
  // 處理錯誤
}

在程式中,建議使用非同步版本。 同步的版本將阻塞整個程式,直到它們完成(停止所有連線)。但是非同步操作不保證執行順序所以,所以在使用時請注意使用物件執行順序。

檔案系統標誌

flags可以是:

'a' - 開啟檔案用於追加。如果檔案不存在,則建立該檔案。
'ax' - 與 'a' 相似,但如果路徑已存在則失敗。
'a+' - 開啟檔案用於讀取和追加。如果檔案不存在,則建立該檔案。
'ax+' - 與 'a+' 相似,但如果路徑已存在則失敗。
'as' - 以同步模式開啟檔案用於追加。如果檔案不存在,則建立該檔案。
'as+' - 以同步模式開啟檔案用於讀取和追加。如果檔案不存在,則建立該檔案。
'r' - 開啟檔案用於讀取。如果檔案不存在,則出現異常。
'r+' - 開啟檔案用於讀取和寫入。如果檔案不存在,則出現異常。
'rs+' - 以同步模式開啟檔案用於讀取和寫入。指示作業系統繞過本地的檔案系統快取。
'w' - 開啟檔案用於寫入。如果檔案不存在則建立檔案,如果檔案已存在則截斷檔案。
'wx' - 與 'w' 相似,但如果路徑已存在則失敗。
'w+' - 開啟檔案用於讀取和寫入。如果檔案不存在則建立檔案,如果檔案已存在則截斷檔案。
'wx+' - 與 'w+' 相似,但如果路徑已存在則失敗。

底層介面

開啟檔案

非同步開啟

// fs.open
path <string> | <Buffer> | <URL>
flags <string> | <number> 參閱支援的檔案系統標誌。
mode <integer> 預設值: 0o666(可讀寫)。
callback <Function>
  - err <Error>
  - fd <integer>
var fs = require('fs')
fs.open('1.txt','r',function(err,fs){
    console.log(err)
    console.log(fs)
})

執行結果:

$ node 1.js
null
3

注意: 使用'rs+'模式不會使fs.open()進入同步阻塞呼叫。如果那是你想要的,則應該使用fs.openSync()

同步開啟

// fs.openSync
path <string> | <Buffer> | <URL>
flags <string> | <number> 參閱支援的檔案系統標誌。
mode <integer> 預設值: 0o666。
返回: <number>
返回表示檔案描述符的整數。
var fs = require('fs');
var result = fs.openSync('1.txt','r');
console.log(result);

執行結果:

$ node 1.js
3

讀取檔案

fd <integer> 從 fd 指定的檔案中讀取資料。
buffer <Buffer> | <TypedArray> | <DataView> 資料將寫入的緩衝區。
offset <integer> buffer 中開始寫入的偏移量
length <integer> 是一個整數,指定要讀取的位元組數
position <integer> 引數指定從檔案中開始讀取的位置。 如果 position 為 null,則從當前檔案位置讀取資料,並更新檔案位置。 如果 position 是整數,則檔案位置將保持不變。
callback <Function>
- err <Error>
- bytesRead <integer>
- buffer <Buffer>
var fs = require('fs');
fs.open('1.txt','r',function(err,fd){
    if(err){
        console.log('檔案開啟失敗');
    }else{
        var bf = Buffer.alloc(5);
        fs.read(fd,bf,0,3,null,function(err,len,buffer){
            console.log(err);
            console.log(len);
            console.log(buffer);
        })
    }
});

執行結果:

$ node 1.js
null
3
<Buffer 68 65 6c 00 00>

同步讀取

fd <integer>
buffer <Buffer> | <TypedArray> | <DataView>
offset <integer>
length <integer>
position <integer>
返回: <number>
返回 bytesRead 的數量。
var fs = require('fs');
var fd = fs.openSync('1.txt','r');
var bf = Buffer.alloc(5);
var result = fs.readSync(fd,bf,0,3,null);
console.log(result);

執行結果:

$ node 1.js
3

寫入檔案

fd <Integer>  檔案標識
buffer <String> | <Buffer> 要將buffer中的資料寫入到檔案中
offset <Integer> buffer物件中要寫入的資料的起始位置
length <Integer> length是一個整數,指定要寫入的位元組數
position <Integer> 指定從檔案開始寫入資料的位置的偏移量。 如果 typeof position !== 'number',則資料從當前位置寫入
callback <Function> 回撥有三個引數(err, written, buffer),其中written指定從buffer寫入了多少位元組

執行結果

$ node 1.js
null
3
<Buffer 74 65 73 74>

[注意]多次對同一檔案使用fs.write且不等待回撥,是不安全的。對於這種情況,強烈推薦使用 fs.createWriteStream  當我們要對開啟的檔案進行寫操作的時候,開啟檔案的模式應該是讀寫模式

同步寫入

fd <integer>
buffer <Buffer> | <TypedArray> | <DataView>
offset <integer>
length <integer>
position <integer>
返回: <number> 寫入的位元組數。
var fs = require('fs');
var fd = fs.openSync('1.txt','r+');
var bf = Buffer.alloc(5);
var result = fs.writeSync(fd,bf,0,3,null);
console.log(result);

執行結果:

$ node 1.js
3

關閉檔案

fd - 通過 fs.open() 方法返回的檔案描述符。
callback - 回撥函式,沒有引數。
var fs = require('fs');
fs.open('1.txt','r+',function(err,fd){
    if(err){
        console.log('檔案開啟失敗');
    }else{
        fs.close(fd, function(err){
            if (err){
                console.log(err);
            } 
            console.log("檔案關閉成功");
        });
    }
});
$ node 1.js
檔案關閉成功

同步關閉

var fs = require('fs');
var fd = fs.openSync('1.txt','r+');
fs.closeSync(fd);

檔案操作

檔案讀取

同步讀取

// fs.readFileSync
path <string> | <Buffer> | <URL>
options <string> | <Object>
    encoding <string> 預設值: 'utf8'。
    withFileTypes <boolean> 預設值: false。

返回: <string[]> | <Buffer[]> | <fs.Dirent[]>
var fs = require('fs');
var data;

try{
    data = fs.readFileSync('./1.txt', 'utf8');
    console.log('檔案內容: ' + data);
}catch(err){
    console.error('讀取檔案出錯: ' + err.message);
}

執行結果為:

$ node 1.js
檔案內容: hello rock

非同步讀取

// fs.readFile
path <string> | <Buffer> | <URL>
options <string> | <Object>
    - encoding <string> 預設值: 'utf8'。
    - withFileTypes <boolean> 預設值: false。
callback <Function>
    - err <Error>
    - files <string[]> | <Buffer[]> | <fs.Dirent[]>
var fs = require('fs');

fs.readFile('./1.txt', 'utf8', function(err, data){
    if(err){
        return console.error('讀取檔案出錯: ' + err.message);
    }
    console.log('檔案內容: ' + data);
});

執行結果也為:

$ node 1.js
檔案內容: hello rock

通過檔案流讀取

適合讀取大檔案

path <string> | <Buffer> | <URL>
options <string> | <Object>
   - flags <string> 參閱支援的檔案系統標誌。預設值: 'r'。
   - encoding <string> 預設值: null。
   - fd <integer> 預設值: null。
   - mode <integer> 預設值: 0o666。
   - autoClose <boolean> 預設值: true。
   - start <integer>
   - end <integer> 預設值: Infinity。
   - highWaterMark <integer> 預設值: 64 * 1024。
返回: <fs.ReadStream> 參閱[可讀流]。
var fs = require('fs');
var readStream = fs.createReadStream('./1.txt', 'utf8');

readStream
    .on('data', function(chunk) {
        console.log('讀取資料: ' + chunk);
    })
    .on('error', function(err){
        console.log('出錯: ' + err.message);
    })
    .on('end', function(){  // 沒有資料了
        console.log('沒有資料了');
    })
    .on('close', function(){  // 已經關閉,不會再有事件丟擲
        console.log('已經關閉');
    });

執行結果:

$ node 1.js
讀取資料: hello rock
沒有資料了
已經關閉

檔案寫入

非同步寫入

// fs.writeFile
file <string> | <Buffer> | <URL> | <integer> 檔名或檔案描述符。
data <string> | <Buffer> | <TypedArray> | <DataView>
options <Object> | <string>
  - encoding <string> | <null> 預設值: 'utf8'。
  - mode <integer> 預設值: 0o666。
  - flag <string> 參閱支援的檔案系統標誌。預設值: 'w'。
callback <Function>
  - err <Error></Error>
var fs = require('fs');
fs.writeFile('./1.txt', 'hello rock', 'utf8', function(err){
    if(err) throw err;
    console.log('檔案寫入成功');
});

執行結果:

$ node 1.js
檔案寫入成功

同步寫入

// fs.writeFileSync
file <string> | <Buffer> | <URL> | <integer> 檔名或檔案描述符。
data <string> | <Buffer> | <TypedArray> | <DataView>
options <Object> | <string>
  - encoding <string> | <null> 預設值: 'utf8'。
  - mode <integer> 預設值: 0o666。
  - flag <string> 參閱支援的檔案系統標誌。預設值: 'w'。
  
返回 undefined。
var fs = require('fs');
try{
    fs.writeFileSync('./1.txt', 'hello rock', 'utf8');
    console.log('檔案寫入成功');
}catch(err){
    throw err;
}

執行結果:

$ node 1.js
檔案寫入成功

通過檔案流寫入

path <string> | <Buffer> | <URL>
options <string> | <Object>
   - flags <string> 參閱支援的檔案系統標誌。預設值: 'w'。
   - encoding <string> 預設值: 'utf8'。
   - fd <integer> 預設值: null。
   - mode <integer> 預設值: 0o666。
   - autoClose <boolean> 預設值: true。
   - start <integer>
返回: <fs.WriteStream> 參閱可寫流。
var fs = require('fs');
var writeStream = fs.createWriteStream('./1.txt', 'utf8');
writeStream
    .on('close', function(){ // 已經關閉,不會再有事件丟擲
        console.log('已經關閉');
    });
writeStream.write('hello');
writeStream.write('rock');
writeStream.end('');

執行結果:

$ node 1.js
已經關閉

追加檔案

file - 檔名或檔案描述符。
data - 要寫入檔案的資料,可以是 String(字串) 或 Buffer(流) 物件。
options - 該引數是一個物件,包含 {encoding, mode, flag}。預設編碼為 utf8, 模式為 0666 , flag 為 'w'
callback - 回撥函式,回撥函式只包含錯誤資訊引數(err),在寫入失敗時返回。
var fs = require('fs');
var filename = '1.txt';
fs.appendFile(filename,' world',function(err){
    console.log(err);
})

執行結果

$ node 1.js
null

同步追加

var fs = require('fs');
var filename = '1.txt';
fs.appendFileSync(filename,' lalala');

刪除檔案

path - 檔案路徑。
callback - 回撥函式,沒有引數。
var fs = require('fs');
var filename = '1.txt';
fs.unlink(filename, function(err) {
   if (err) {
       return console.log('刪除失敗');
   }
   console.log("刪除成功");
});

執行結果:

$ node 1.js
刪除成功

同步刪除

var fs = require('fs');
var filename = '1.txt';
fs.unlink(filename);

重新命名

oldPath <String> | <Buffer>
newPath <String> | <Buffer>
callback <Function> 回撥只有一個可能的異常引數
var fs = require('fs');
var filename = 'a.txt';
fs.rename(filename,'2.new.txt',function(err){
    console.log(err);
})
$ node 1.js
null

同步重新命名

var fs = require('fs');
var filename = '2.new.txt';
var result = fs.renameSync(filename,'a.txt');

檔案資訊

path <string> | <Buffer> | <URL>
options <Object>
bigint <boolean> 返回的 fs.Stats 物件中的數值是否應為 bigint 型。預設值: false。
callback <Function>
   - err <Error>
   - stats <fs.Stats>
var fs = require('fs');
var filename = 'a.txt';
fs.stat(filename,function(err,stats){
    console.log(err);
    console.log(stats);
});

執行結果

$ node 1.js
null
Stats {
  dev: 163689085,
  mode: 33206,
  nlink: 1,
  uid: 0,
  gid: 0,
  rdev: 0,
  blksize: undefined,
  ino: 9007199254854088,
  size: 0,
  blocks: undefined,
  atimeMs: 1562684836201.136,
  mtimeMs: 1562684836201.136,
  ctimeMs: 1562684998231.913,
  birthtimeMs: 1562684836201.136,
  atime: 2019-07-09T15:07:16.201Z,
  mtime: 2019-07-09T15:07:16.201Z,
  ctime: 2019-07-09T15:09:58.232Z,
  birthtime: 2019-07-09T15:07:16.201Z }

stats類中的方法有

stats.isFile()  如果是檔案返回 true,否則返回 false。
stats.isDirectory() 如果是目錄返回 true,否則返回 false。
stats.isBlockDevice()   如果是塊裝置返回 true,否則返回 false。
stats.isCharacterDevice()   如果是字元裝置返回 true,否則返回 false。
stats.isSymbolicLink()  如果是軟連結返回 true,否則返回 false。
stats.isFIFO()  如果是FIFO,返回true,否則返回false。FIFO是UNIX中的一種特殊型別的命令管道。
stats.isSocket()    如果是 Socket 返回 true,否則返回 false。
var fs = require('fs');
var filename = 'a.txt';
fs.stat(filename,function(err,stats){
    console.log(stats.isFile());//true
});

監聽

filename <String> | <Buffer>
options <String> | <Object> 引數可選,如果options是一個字串,則它指定了encoding。否則options應該以一個物件傳入
    persistent <Boolean> 指明如果檔案正在被監視,程式是否應該繼續執行。預設為true
    recursive <Boolean> 指明是否全部子目錄應該被監視,或只是當前目錄。 適用於當一個目錄被指定時,且只在支援的平臺。預設為false
    encoding <String> 指定用於傳給監聽器的檔名的字元編碼。預設為'utf8'
listener <Function> 回撥函式有兩個引數 (eventType, filename)。 eventType可以是'rename'或'change',filename是觸發事件的檔案的名稱
fs.watch('txt', (eventType, filename) => {
  console.log(`事件型別是: ${eventType}`);
  if (filename) {
    console.log(`提供的檔名: ${filename}`);
  } else {
    console.log('未提供檔名');
  }
});
var fs = require('fs');
var filename = '1.txt';
fs.watch(filename,function(eventType, _filename){
    console.log(eventType);//change
    if(_filename){
        console.log(_filename + '發生了改變');//'1.txt發生了改變'
    }else{
        console.log('...');
    }
    
})

[注意]當一個檔案出現或消失在一個目錄裡時,'rename'也會被觸發

相關文章