首頁  >  文章  >  web前端  >  Node.js檔案操作詳解_node.js

Node.js檔案操作詳解_node.js

WBOY
WBOY原創
2016-05-16 16:39:391141瀏覽

Node有一組資料流API,可以像處理網路流那樣處理文件,用起來很方便,但是它只允許順序處理文件,不能隨機讀寫文件。因此,需要使用一些更底層的檔案系統操作。

本章涵蓋了文件處理的基礎知識,包括如何開啟文件,讀取文件某一部分,寫數據,以及關閉文件。

Node的許多檔案API幾乎是UNIX(POSIX)中對應檔案API 的翻版,例如使用檔案描述子的方式,就像UNIX裡一樣,檔案描述子在Node裡也是一個整數數字,代表一個實體在進程檔案描述符表裡的索引。

有3個特殊的文件描述符-1,2和3。他們分別代表標準輸入,標準輸出和標準錯誤檔案描述符。標準輸入,顧名思義,是個唯讀流,進程用它來從控制台或進程通道讀取資料。標準輸出和標準錯誤是僅用來輸出資料的檔案描述符,他們經常被用來向控制台,其它進程或檔案輸出資料。標準錯誤負責錯誤訊息輸出,而標準輸出負責普通的進程輸出。

一旦進程啟動完畢,就能使用這幾個檔案描述符了,它們其實並不存在對應的實體檔案。你不能讀寫某個隨機位置的數據,(譯者註:原文是You can write to and read from specific positions within the file.根據上下文,作者可能少寫了個「not」),只能像操作網絡資料流那樣順序的讀取和輸出,已寫入的資料就不能再修改了。

普通文件不受這種限制,例如Node裡,你即可以創建只能向尾部追加資料的文件,還可以創建讀寫隨機位置的文件。

幾乎所有跟文件相關的操作都會涉及到處理文件路徑,本章先將介紹這些工具函數,然後再深入講解文件讀寫和資料操作

處理檔案路徑

檔案路徑分為相對路徑和絕對路徑兩種,用它們來表示具體的檔案。你可以合併文件路徑,可以提取文件名信息,甚至可以檢測文件是否存在。

Node裡,可以用字串來操處理檔案路徑,但是那樣會使問題變得複雜,例如你要連接路徑的不同部分,有些部分以「/」結尾有些卻沒有,而且路徑分割符在不同作業系統裡也可能會不一樣,所以,當你連接它們時,程式碼就會非常囉嗦和麻煩。

幸運的是,Node有個叫path的模組,可以幫你標準化,連接,解析路徑,從絕對路徑轉換到相對路徑,從路徑中提取各部分信息,檢測文件是否存在。總的來說,path模組其實只是些字串處理,而且也不會到檔案系統去做驗證(path.exists函數例外)。

路徑的標準化

在儲存或使用路徑之前將它們標準化通常是個好主意。例如,由使用者輸入或設定檔所獲得的檔案路徑,或是由兩個或多個路徑連接起來的路徑,一般都應該被標準化。可以用path模組的normalize函數來標準化一個路徑,而且它還能處理“..”,“.”“//”。如:

複製程式碼 程式碼如下:

var path = require('path');

path.normalize('/foo/bar//baz/asdf/quux/..');

// => '/foo/bar/baz/asdf'

連接路徑

使用path.join()函數,可以連接任意多個路徑字串,只用把所有路徑字串依序傳遞給join()函數就可以:

複製程式碼 程式碼如下:

                   var path = require('path');

                   path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');

                   // => '/foo/bar/baz/asdf'

如你所見,path.join()內部會自動將路徑標準化。

解析路徑

用path.resolve()可以把多條路徑解析為一個絕對路徑。它的功能就像對這些路徑挨個不斷進行「cd」操作,和cd指令的參數不同,這些路徑可以是文件,而且它們不必真實存在——path.resolve()方法不會去存取底層文件系統來確定路徑是否存在,它只是一些字串操作。

例如:

複製程式碼 程式碼如下:

                   var path = require('path');

                   path.resolve('/foo/bar', './baz');

                   // =>/foo/bar/baz

                   path.resolve('/foo/bar', '/tmp/file/');

                   // =>/tmp/file

如果解析結果不是絕對路徑,path.resolve()會把目前工作目錄當作路徑附加到解析結果前面,例如:

複製程式碼 程式碼如下:

        path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');
        // 如果目前工作目錄是/home/myself/node, 將會回傳
        // => /home/myself/node/wwwroot/static_files/gif/image.gif'

計算兩條絕對路徑的相對路徑

path.relative()可以告訴你如果從一個絕對位址跳到另一個絕對位址,例如:

複製程式碼 程式碼如下:

                var path = require('path');

                path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');

                // => ../../impl/bbb

從路徑擷取資料

以路徑“/foo/bar/myfile.txt”為例,如果你想獲取父目錄(/foo/bar)的所有內容,或者讀取同級目錄的其它文件,為此,你必須用path.dirname(filePath)取得檔案路徑的目錄部分,例如:

複製程式碼 程式碼如下:

                   var path = require('path');

                   path.dirname('/foo/bar/baz/asdf/quux.txt');

                   // =>/foo/bar/baz/asdf

 或者,你想從檔案路徑裡得到檔名,也就是檔案路徑的最後一部分,可以使用path.basename函數:
 

複製程式碼 程式碼如下:

                    var path = require('path');

                   path.basename('/foo/bar/baz/asdf/quux.html')

                   // => quux.html
 

檔案路徑裡可能還包含檔案副檔名,通常是檔案名稱中最後一個「.」字元之後的那部分字串。

path.basename也可以接受一個副檔名字串作為第二個參數,這樣傳回的檔名就會自動去掉副檔名,只回傳檔案的名稱部分:

複製程式碼 程式碼如下:

                   var path = require('path');

                   path.basename('/foo/bar/baz/asdf/quux.html', '.html');

                   // => quux

 要想這麼做你首先還要知道檔案的副檔名,可以用path.extname()來取得副檔名:

複製程式碼 程式碼如下:

                   var path = require('path');

                   path.extname('/a/b/index.html');

                   // => '.html'

                   path.extname('/a/b.c/index');

                   // => ''

                   path.extname('/a/b.c/.');

                   // => ''

                   path.extname('/a/b.c/d.');

                   // => '.'

檢查路徑是否存在

目前為止,前面涉及到的路徑處理操作都跟底層檔案系統無關,只是一些字串操作。然而,有些時候你需要判斷一個檔案路徑是否存在,例如,你有時候需要判斷檔案或目錄是否存在,如果不存在的話才建立它,可以用path.exsits():

複製程式碼 程式碼如下:

                   var path = require('path');

                   path.exists('/etc/passwd', function(exists) {

                            console.log('exists:', exists);

;

                            // => true

                   });

                   path.exists('/does_not_exist', function(exists) {

                            console.log('exists:', exists);

;

                            // => 中

                   });

注意:從Node0.8版本開始,exists從path模組移到了fs模組,變成了fs.exists,除了命名空間不同,其它都沒變:

複製程式碼 程式碼如下:

                   var fs = require('fs');

                   fs.exists('/does_not_exist', function(exists) {

                            console.log('exists:', exists);

;

                            // => 中


                   });

path.exists()是個I/O操作,因為它是非同步的,因此需要一個回呼函數,當I/O操作返回後呼叫這個回調函數,並且把結果傳遞給它。你也可以使用它的同步版本path.existsSync(),功能完全一樣,只是它不會呼叫回呼函數,而是直接回傳結果:

程式碼如下:

                  var path = require('path');

                 path.existsSync('/etc/passwd');


                 // => true

fs模組介紹


fs模組包含所有文件查詢和處理的相關函數,用這些函數,可以查詢文件信息,讀寫和關閉文件。這樣導入fs模組:

程式碼如下:


         var fs = require(‘fs')

查詢文件資訊


有時你可能需要知道文件的大小,創建日期或權限等文件信息,可以使用fs.stath函數來查詢文件或目錄的元信息:

程式碼如下:

                   var fs = require('fs');

                  fs.stat('/etc/passwd', function(err, stats) {

                                  

                                  

                  });


這塊程式碼片段會有類似下面的輸出


複製程式碼 程式碼如下:
 { dev: 234881026,
ino: 95028917,

mode: 33188,

nlink: 1,

uid: 0,

gid: 0,

rdev: 0,

size: 5086,

blksize: 4096,

blocks: 0,

atime: Fri, 18 Nov 2011 22:44:47 GMT,

mtime: Thu, 08 Sep 2011 23:50:04 GMT,

ctime: Thu, 08 Sep 2011 23:50:04 GMT }


1.fs.stat()呼叫會將一個stats類別的實例作為參數傳遞給它的回調函數,可以像下面這樣使用stats實例:

2.stats.isFile() —— 如果是標準文件,而不是目錄,socket,符號連結或設備,則傳回true,否則false
3.stats.isDiretory() —— 如果是目錄則回傳tue,否則false
4.stats.isBlockDevice() —— 如果是區塊裝置則回傳true,在大多數UNIX系統中區塊裝置通常都在/dev目錄下
5.stats.isChracterDevice() —— 如果是字元裝置回傳true
6.stats.isSymbolickLink() —— 如果是檔案連結回傳true
7.stats.isFifo() —— 如果是FIFO(UNIX命名管道的一個特殊類型)回傳true
8.stats.isSocket() —— 如果是個UNIX socket(TODO:googe it)

開啟檔案

在讀取或處理文件之前,必須先使用fs.open函數打開文件,然後你提供的回調函數會被調用,並得到這個文件的描述符,稍後你可以用這個文件描述符來讀寫這個已經開啟的檔案:

複製程式碼 程式碼如下:

                   var fs = require('fs');

                   fs.open('/path/to/file', 'r', function(err, fd) {

                        // got fd file descriptor

                   });

fs.open的第一個參數是檔案路徑,第二個參數是一些用來指示以什麼模式開啟檔案的標記,這些標記可以是r,r ,w,w ,a或a 。以下是這些標記的說明(來自UNIX文件的fopen頁)

1.r —— 以唯讀方式開啟文件,資料流的初始位置在文件開始
2.r —— 以可讀寫方式開啟文件,資料流的初始位置在文件開始
3.w ——如果文件存在,則將文件長度清0,即該文件內容會遺失。如果不存在,則嘗試建立它。資料流的初始位置在檔案開始
4.w —— 以可讀寫方式開啟文件,如果文件不存在,則嘗試建立它,如果文件存在,則將文件長度清除,即該文件內容會遺失。資料流的初始位置在檔案開始
5.a —— 以只寫方式開啟文件,如果文件不存在,則嘗試建立它,資料流的初始位置在文件末尾,隨後的每次寫入操作都會將資料追加到文件後面。
6.a ——以可讀寫方式開啟文件,如果文件不存在,則嘗試建立它,資料流的初始位置在文件末尾,隨後的每次寫入操作都會將資料追加到文件後面。

讀取檔案

一旦開啟了文件,就可以開始讀取文件內容,但是在開始之前,你得先創建一個緩衝區(buffer)來放置這些資料。這個緩衝區物件將會以參數形式傳遞給fs.read函數,並被fs.read填入上資料。

複製程式碼 程式碼如下:

var fs = require('fs');

fs.open('./my_file.txt', 'r', 函數開啟(err, fd) {

if (err) { 拋出錯誤 }

var readBuffer = new Buffer(1024),

bufferOffset = 0,

bufferLength = readBuffer.length,

檔案位置 = 100;

fs.read(fd,

         readBuffer,

         bufferOffset,

         bufferLength,

         檔案位置,

         函數 read(err, readBytes) {

                   if (err) { throw err; }

                  在已讀出' readBytes '位元組');

                   if (readBytes > 0) {

                            console.log(readBuffer.slice(0, readBytes));

                   }

});

});

上面程式碼嘗試開啟一個文件,當開啟成功後(呼叫開啟的函數),開始請求從文件流第100個位元組讀取開始附帶1024個位元組的資料(第11行)。

fs.read()的最後一個參數是個回呼函數(第16行),當下面發生異常情況時,它會被呼叫:

1.發生錯誤
2.成功讀取了資料
3.沒有資料差異

如果發生錯誤,第一個參數(err)會為回呼函數提供一個包含錯誤訊息的對象,否則這個參數為null。如果成功讀取了數據,第二個參數(readBytes)會指明被讀取到塔里數據的大小,如果值為0,則表示到達了檔案結構。

注意:一旦把蠟燭圖物件傳遞給fs.open(),緩衝物件的控制權就轉移給了read指令,只有當回呼函數被呼叫時,蠟燭圖物件的控制權才會回到你手因此在這之前,不要讓讀寫器或讓其他函數呼叫使用這個瀑布物件裡;否則,你可能會讀到不完整的數據,更糟糕的情況是,你可能會並發地到這個瀑布物件裡寫入數據。

寫文件

透過傳遞給fs.write()傳遞一個包含資料的緩衝對象,來往一個已開啟的檔案裡寫入資料:

複製程式碼程式碼如下:

var fs = require('fs');

fs.open('./my_file.txt', 'a', 函數開啟(err, fd) {

    if (err) { throw err; }

    var writeBuffer = new Buffer('寫入此字串'),

   bufferPosition = 0,

   bufferLength = writeBuffer.length, filePosition = null;

    fs.write( fd,

        writeBuffer,

        bufferPosition,

        bufferLength,

        檔案位置,

        函數 write(錯誤,已寫) {

           if (err) { throw err; }

           console.log('已寫了'寫了'字節');

        });

});

這個例子裡,第2(譯者註:原文為3)行程式碼嘗試用追加模式(a)開啟一個文件,然後第7行程式碼(譯者註:原文為9)寫入資料到資料。緩衝區物件需要附帶幾個資訊一起做為參數:

1.緩衝區的資料
2.待寫資料從緩衝區的什麼位置開始
3.待寫資料的長度
4.資料寫到檔案的哪個位置
5.當操作結束後被呼叫的回調函數wrote

這個例子裡,filePostion參數為null,也就是說write函數會把資料寫到檔案指標目前所在的位置,因為是以追加模式開啟的文件,因此文件指標在檔案結尾。

跟read操作一樣,千萬不要在fs.write執行過程中使用哪個傳入的緩衝區對象,一旦fs.write開始執行它就獲得了那個緩衝區對象的控制權。你只能等到回調函數被呼叫後才能再重新使用它。

關閉檔案

你可能注意到了,到目前為止,本章的所有範例都沒有關閉檔案的程式碼。因為它們只是些只使用一次而且又小又簡單的例子,當Node進程結束時,作業系統會確保關閉所有檔案。

但是,在實際的應用程式中,一旦打開一個檔案你要確保最終關閉它。要做到這一點,你需要追蹤所有那些已開啟的檔案描述符,然後在不再使用它們的時候呼叫fs.close(fd[,callback])來最終關閉它們。如果你不仔細的話,很容易就會遺漏某個文件描述符。下面的範例提供了一個叫做openAndWriteToSystemLog的函數,展示如何小心的關閉檔案:

複製程式碼 程式碼如下:

var fs = require('fs');
function openAndWriteToSystemLog(writeBuffer, callback){
    fs.open('./my_file', 'a', function opened(err, fd) {
        if (err) { return callback(err); }
        function notifyError(err) {
            fs.close(fd, function() {
                callback(err);
            });
        }
        var bufferOffset = 0,
        bufferLength = writeBuffer.length,
        filePosition = null;
        fs.write( fd, writeBuffer, bufferOffset, bufferLength, filePosition,
            function wrote(err, written) {
                if (err) { return notifyError(err); }
                fs.close(fd, function() {
                    callback(err);
                });
            }
        );
    });
}
openAndWriteToSystemLog(
    new Buffer('writing this string'),
    function done(err) {
        if (err) {
            console.log("error while opening and writing:", err.message);
            return;
        }
        console.log('All done with no errors');
    }
);

  在這兒,提供了一個叫openAndWriteToSystemLog的函數,它接受一個包含待寫資料的緩衝區對象,以及一個操作完成或出錯後被調用的回調函數,如果有錯誤發生,回呼函數的第一個參數會包含這個錯誤物件。

注意那個內部函數notifyError,它會關閉文件,並報告發生的錯誤。

注意:到此為止,你知道如何使用底層的原子操作來打開,讀,寫和關閉文件。然而,Node還有一組更高階的建構函數,讓你可以用更簡單的方式來處理檔案。

例如,你想用一種安全的方式,讓兩個或多個write操作並發的往一個檔案裡追加數據,這時你可以使用WriteStream。

還有,如果你想讀取一個檔案的某個區域,可以考慮使用ReadStream。這兩種用例會在第九章「資料的讀,寫流」裡介紹。

小結

當你使用文件時,多數情況下都需要處理和提取文件路徑信息,透過使用path模組你可以連接路徑,標準化路徑,計算路徑的差別,以及將相對路徑轉化成絕對路徑。你可以提取指定檔案路徑的副檔名,檔案名,目錄等路徑元件。

Node在fs模組裡提供了一套底層API來存取檔案系統,底層API使用檔案描述子來操作檔案。你可以用fs.open開啟文件,用fs.write寫文件,用fs.read讀取文件,並用fs.close關閉文件。

當有錯誤發生時,你應該總是使用正確的錯誤處理邏輯來關閉檔案-以確保在呼叫返回之前關閉那些已開啟的檔案描述符。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn