人所缺少的不是才幹而是志向,不是成功的能力而是勤勞的意志。 —— 部爾衛
文章同步到github博客:https://github.com/koala-codi...javascript
文件操做是開發過程當中並不可少的一部分。Node.js 中的 fs 模塊是文件操做的封裝,它提供了文件讀取、寫入、改名、刪除、遍歷目錄、連接等 POSIX 文件系統操做。與其它模塊不一樣的是,fs 模塊中全部的操做都提供了異步和同步的兩個版本,具備 sync 後綴的方法爲同步方法,不具備 sync 後綴的方法爲異步方法java
-- 權限位 modelinux
-- 標識位 flaggit
-- 文件描述符 fs程序員
-- 常規文件操做github
-- 高級文件操做面試
-- 文件目錄操縱api
說幾個fs
模塊的經常使用函數?什麼狀況下使用fs.open
的方式讀取文件?用fs
模塊寫一個大文件拷貝的例子(注意大文件)?
計算機中的一些文件知識,文件的權限位 mode、標識位 flag、文件描述符 fd等你有必要了解下。這些內容對於你接下來學習 fs 的 api ,記憶和使用都會有不少幫助。數組
由於 fs 模塊須要對文件進行操做,會涉及到操做權限的問題,因此須要先清楚文件權限是什麼,都有哪些權限。緩存
文件權限表:
在上面表格中,咱們能夠看出系統中針對三種類型進行權限分配,即文件全部者(本身)、文件所屬組(家人)和其餘用戶(陌生人),文件操做權限又分爲三種,讀、寫和執行,數字表示爲八進制數,具有權限的八進制數分別爲 4
、2
、1
,不具有權限爲 0。
爲了更容易理解,咱們能夠隨便在一個目錄中打開 Git
,使用 Linux
命令 ls -al
來查目錄中文件和文件夾的權限位
drwxr-xr-x 1 koala 197121 0 Jun 28 14:41 core -rw-r--r-- 1 koala 197121 293 Jun 23 17:44 index.md
在上面的目錄信息當中,很容易看出用戶名、建立時間和文件名等信息,但最重要的是開頭第一項(十位的字符)。
第一位表明是文件仍是文件夾,d
開頭表明文件夾,-
開頭的表明文件,然後面九位就表明當前用戶、用戶所屬組和其餘用戶的權限位,按每三位劃分,分別表明讀(r)、寫(w)和執行(x),-
表明沒有當前位對應的權限。
權限參數 mode 主要針對 Linux 和 Unix 操做系統,Window 的權限默認是可讀、可寫、不可執行,因此權限位數字表示爲 0o666,轉換十進制表示爲 438。
Node.js 中,標識位表明着對文件的操做方式,如可讀、可寫、便可讀又可寫等等,在下面用一張表來表示文件操做的標識位和其對應的含義。
符號 | 含義 |
---|---|
r | 讀取文件,若是文件不存在則拋出異常。 |
r+ | 讀取並寫入文件,若是文件不存在則拋出異常。 |
rs | 讀取並寫入文件,指示操做系統繞開本地文件系統緩存。 |
w | 寫入文件,文件不存在會被建立,存在則清空後寫入。 |
wx | 寫入文件,排它方式打開。 |
w+ | 讀取並寫入文件,文件不存在則建立文件,存在則清空後寫入。 |
wx+ | 和 w+ 相似,排他方式打開。 |
a | 追加寫入,文件不存在則建立文件。 |
ax | 與 a 相似,排他方式打開。 |
a+ | 讀取並追加寫入,不存在則建立。 |
ax+ | 與 a+ 相似,排他方式打開。 |
上面表格就是這些標識位的具體字符和含義,可是 flag 是不常用的,不容易被記住,因此在下面總結了一個加速記憶的方法。
r+ 和 w+ 的區別,當文件不存在時,r+ 不會建立文件,而會拋出異常,但 w+ 會建立文件;若是文件存在,r+ 不會自動清空文件,但 w+ 會自動把已有文件的內容清空。
操做系統會爲每一個打開的文件分配一個名爲文件描述符的數值標識,文件操做使用這些文件描述符來識別與追蹤每一個特定的文件,Window 系統使用了一個不一樣但概念相似的機制來追蹤資源,爲方便用戶,NodeJS 抽象了不一樣操做系統間的差別,爲全部打開的文件分配了數值的文件描述符。
在 Node.js 中,每操做一個文件,文件描述符是遞增的,文件描述符通常從 3 開始,由於前面有 0、一、2 三個比較特殊的描述符,分別表明 process.stdin
(標準輸入)、process.stdout
(標準輸出)和 process.stderr
(錯誤輸出)。
fs.readFile(filename,[encoding],[callback(error,data)]
文件讀取函數
callback
是回調函數,用於接收文件的內容。說明:若是不指定 encoding ,則callback
就是第二個參數。
回調函數提供兩個參數 err 和 data , err 表示有沒有錯誤發生,data 是文件內容。
若是指定 encoding , data是一個解析後的字符串,不然將會以 Buffer 形式表示的二進制數據。
demo:
const fs = require('fs'); const path = require('path'); const filePath = path.join(__dirname,'koalaFile.txt') const filePath1 = path.join(__dirname,'koalaFile1.txt') // -- 異步讀取文件 fs.readFile(filePath,'utf8',function(err,data){ console.log(data);// 程序員成長指北 }); // -- 同步讀取文件 const fileResult=fs.readFileSync(filePath,'utf8'); console.log(fileResult);// 程序員成長指北
fs.writeFile(filename,data,[options],callback)
文件寫入操做
encoding {String | null} default='utf-8' mode {Number} default=438(aka 0666 in Octal) flag {String} default='w'
這個時候第一章節講的計算機知識就用到了,flag值,默認爲w,會清空文件,而後再寫。flag值,r表明讀取文件,w表明寫文件,a表明追加。
demo:
// 寫入文件內容(若是文件不存在會建立一個文件) // 寫入時會先清空文件 fs.writeFile(filePath, '寫入成功:程序員成長指北', function(err) { if (err) { throw err; } // 寫入成功後讀取測試 var data=fs.readFileSync(filePath, 'utf-8'); console.log('new data -->'+data); }); // 經過文件寫入而且利用flag也能夠實現文件追加 fs.writeFile(filePath, '程序員成長指北追加的數據', {'flag':'a'},function(err) { if (err) { throw err; } console.log('success'); var data=fs.readFileSync(filePath, 'utf-8') // 寫入成功後讀取測試 console.log('追加後的數據 -->'+data); });
fs.appendFile(filename, data, [options], callback)
說明:該方法以異步的方式將 data 插入到文件裏,若是文件不存在會自動建立
demo:
// -- 異步另外一種文件追加操做(非覆蓋方式) // 寫入文件內容(若是文件不存在會建立一個文件) fs.appendFile(filePath, '新數據程序員成長指北456', function(err) { if (err) { throw err; } // 寫入成功後讀取測試 var data=fs.readFileSync(filePath, 'utf-8'); console.log(data); }); // -- 同步另外一種文件追加操做(非覆蓋方式) fs.appendFileSync(filePath, '同步追加一條新數據程序員成長指北789');
fs.copyFile(filenameA, filenameB,callback)
demo:
// 將filePath文件內容拷貝到filePath1文件內容 fs.copyFileSync(filePath, filePath1); let data = fs.readFileSync(filePath1, 'utf8'); console.log(data); // 程序員成長指北
fs.unlink(filename, callback)
demo:
// -- 異步文件刪除 fs.unlink(filePath,function(err){ if(err) return; }); // -- 同步刪除文件 fs.unlinkSync(filePath,function(err){ if(err) return; });
接下來的高級文件操做會與上面有些不一樣,流程稍微複雜一些,要先用fs.open
來打開文件,而後才能夠用fs.read
去讀,或者用fs.write
去寫文件,最後,你須要用fs.close
去關掉文件。
特殊說明:read 方法與 readFile 不一樣,通常針對於文件太大,沒法一次性讀取所有內容到緩存中或文件大小未知的狀況,都是屢次讀取到 Buffer 中。
想了解 Buffer 能夠看 NodeJS —— Buffer 解讀。(注意這裏換成個人文章)
fs.open(path,flags,[mode],callback)
第一個參數:文件路徑
第二個參數:與開篇說的標識符 flag 相同
第三個參數:[mode] 是文件的權限(可選參數,默認值是0666)
第四個參數:callback 回調函數
demo:
fs.open(filePath,'r','0666',function(err,fd){ console.log('哈哈哈',fd); //返回的第二個參數爲一個整數,表示打開文件返回的文件描述符,window中又稱文件句柄 })
demo 說明:返回的第二個參數爲一個整數,表示打開文件返回的文件描述符,window中又稱文件句柄,在開篇也有對文件描述符
說明。
fs.read(fd, buffer, offset, length, position, callback);
六個參數
fs.open
打開成功後返回的文件描述符;v8
引擎分配的一段內存,要將內容讀取到的 Buffer;demo:
const fs = require('fs'); let buf = Buffer.alloc(6);// 建立6字節長度的buf緩存對象 // 打開文件 fs.open('6.txt', 'r', (err, fd) => { // 讀取文件 fs.read(fd, buf, 0, 3, 0, (err, bytesRead, buffer) => { console.log(bytesRead); console.log(buffer); // 繼續讀取 fs.read(fd, buf, 3, 3, 3, (err, bytesRead, buffer) => { console.log(bytesRead); console.log(buffer); console.log(buffer.toString()); }); }); }); // 3 // <Buffer e4 bd a0 00 00 00> // 3 // <Buffer e4 bd a0 e5 a5 bd> // 你好
fs.write(fd, buffer, offset, length, position, callback);
六個參數
fs.open
打開成功後返回的;v8
引擎分配的一段內存,存儲將要寫入文件數據的 Buffer;demo:
fs.close(fd,callback)
open
時傳遞的文件描述符
demo:
// 注意文件描述符fd fs.open(filePath, 'r', (err, fd) => { fs.close(fd, err => { console.log('關閉成功');// 關閉成功 }); });
一、fs.mkdir 建立目錄
fs.mkdir(path, [options], callback)
mode <integer> Windows 上不支持。默認值: 0o777。 可選的 options 參數能夠是指定模式(權限和粘滯位)的整數,也能夠是具備 mode 屬性和 recursive 屬性(指示是否應建立父文件夾)的對象。
demo:
fs.mkdir('./mkdir',function(err){ if(err) return; console.log('建立目錄成功'); })
注意:
在 Windows 上,在根目錄上使用 fs.mkdir() (即便使用遞歸參數)也會致使錯誤:
fs.mkdir('/', { recursive: true }, (err) => { // => [Error: EPERM: operation not permitted, mkdir 'C:\'] });
二、fs.rmdir刪除目錄
fs.rmdir(path,callback)
demo:
const fs = require('fs'); fs.rmdir('./mkdir',function(err){ if(err) return; console.log('刪除目錄成功'); })
注意:在文件(而不是目錄)上使用 fs.rmdir() 會致使在 Windows 上出現 ENOENT 錯誤、在 POSIX 上出現 ENOTDIR 錯誤。
三、fs.readdir讀取目錄
fs.readdir(path, [options], callback)
若是 options.withFileTypes 設置爲 true,則 files 數組將包含 fs.Dirent 對象。
'.'
和 '..'
)。demo:
const fs = require('fs'); fs.readdir('./file',function(err,data){ if(err) return; //data爲一個數組 console.log('讀取的數據爲:'+data[0]); });
只講文件相關 Api 顯得很枯燥,下面說一些 fs 在 Node.js 中的具體應用
文件拷貝例子包括小文件拷貝和大文件拷貝(以前講的 fs 模塊也能夠實現文件拷貝)
小文件拷貝除了上面 fs 本身提供的 api 咱們本身也能夠經過讀寫完成一個拷貝例子,以下:
// 文件拷貝 將data.txt文件中的內容拷貝到copyData.txt // 讀取文件 const fileName1 = path.resolve(__dirname, 'data.txt') fs.readFile(fileName1, function (err, data) { if (err) { // 出錯 console.log(err.message) return } // 獲得文件內容 var dataStr = data.toString() // 寫入文件 const fileName2 = path.resolve(__dirname, 'copyData.txt') fs.writeFile(fileName2, dataStr, function (err) { if (err) { // 出錯 console.log(err.message) return } console.log('拷貝成功') }) })
咱們使用 readFile 和 writeFile 實現了一個 copy 函數,那個 copy 函數是將被拷貝文件的數據一次性讀取到內存,一次性寫入到目標文件中,這種針對小文件還好。
若是是一個大文件幾百M一次性讀取寫入不現實,因此須要屢次讀取屢次寫入,接下來使用文件操做的高級方法對大文件和文件大小未知的狀況實現一個 copy 函數。固然除了這種方式還有我在以前的文章講過的stream模塊也能夠實現,並且性能更好,可是這裏就再也不重複說明,本篇主要講fs模塊。
demo:
// copy 方法 function copy(src, dest, size = 16 * 1024, callback) { // 打開源文件 fs.open(src, 'r', (err, readFd) => { // 打開目標文件 fs.open(dest, 'w', (err, writeFd) => { let buf = Buffer.alloc(size); let readed = 0; // 下次讀取文件的位置 let writed = 0; // 下次寫入文件的位置 (function next() { // 讀取 fs.read(readFd, buf, 0, size, readed, (err, bytesRead) => { readed += bytesRead; // 若是都不到內容關閉文件 if (!bytesRead) fs.close(readFd, err => console.log('關閉源文件')); // 寫入 fs.write(writeFd, buf, 0, bytesRead, writed, (err, bytesWritten) => { // 若是沒有內容了同步緩存,並關閉文件後執行回調 if (!bytesWritten) { fs.fsync(writeFd, err => { fs.close(writeFd, err => return !err && callback()); }); } writed += bytesWritten; // 繼續讀取、寫入 next(); }); }); })(); }); }); }
在上面的 copy 方法中,咱們手動維護的下次讀取位置和下次寫入位置,若是參數 readed 和 writed 的位置傳入 null,NodeJS 會自動幫咱們維護這兩個值。
如今有一個文件 6.txt 內容爲 「你好」,一個空文件 7.txt,咱們將 6.txt 的內容寫入 7.txt 中。
const fs = require('fs'); // buffer 的長度 const BUFFER_SIZE = 3; // 拷貝文件內容並寫入 copy('6.txt', '7.txt', BUFFER_SIZE, () => { fs.readFile('7.txt', 'utf8', (err, data) => { // 拷貝完讀取 7.txt 的內容 console.log(data); // 你好 }); });
在 NodeJS 中進行文件操做,屢次讀取和寫入時,通常一次讀取數據大小爲 64k,寫入數據大小爲 16k。
你們好,我是koala,在作一個一個Node.js高級進階路線,今天就分享這麼多,若是對分享的內容感興趣,能夠關注公衆號「程序員成長指北」,或者加入技術交流羣,你們一塊兒討論。