NodeJS學習之文件操作


NodeJS -- 文件操作

Buffer(數據塊)

JS語言自身只有字符串數據類型,沒有二進制數據類型,因此NodeJS提供了一個與String對等的全局構造函數Buffer來提供對二進制數據的操作。除了可以讀取文件得到Buffer的實例外,還能夠直接構造,例如:

var bin = new Buffer([0x68, 0x65, 0x6c, 0x6c, 0x6f]);

Buffer與字符串類似,除了可以用.length屬性得到字節長度外,還可以使用[index]方式讀取指定位置的字節,例:

bin[0]; // => 0x68;

Buffer與字符串能夠互相轉化,例:

var str = bin.toString('utf-8'); // => 'hello'
var bin = new Buffer('hello','utf-8'); // =><Buffer 68 65 6c 6c 6f>

Buffer與字符串一個重要區別:字符串是只讀的,並且對字符串的任何修改得到的都是一個新字符串,原字符串保持不變。至於Buffer,更像是可以做指針操作的C語言數組。例可以使用[index]方式直接修改某個位置的字節:

bin[0] = 0x48;

而.slice方法也不是返回一個新的Buffer,而更像是反回了指向原Buffer中間某個位置的指針:

因此對.slice方法返回的Buffer的修改會作用於原Buffer。

為此若要拷貝一份Buffer,需要先創建一個新的Buffer,通過.copy把原Buffer的數據復制過去,類似於先申請新內存,再把已有內存的數據復制過去。
總之,Buffer將JS數據處理能力擴展到了任意二進制數據

Stream(數據流)

當內存中無法一次裝下需要處理的數據時,或者一邊讀取一遍處理更加高效時,我們就需要用到數據流。NodeJS通過各種Stream來提供對數據流的操作,在大文件拷貝時,可以為數據來源創建一個只讀數據流,例:
var rs = fs.createReadStream(pathName);

rs.on('data', function(chunk) {
    doSomething(chunk);
});

rs.on('end', function() {
    cleanUp();
});

:Stream基於事件機制工作,所有Stream實例都繼承於NodeJS提供的EventEmitter

上邊的代碼中data事件會源源不斷地被觸發,不管doSomething()函數是否處理得過來。代碼可以繼續作如下改造,以解決這個問題:

var rs = fs.createReadStream(src);

rs.on('data', function(chunk) {
    rs.pause();
    doSomething(chunk, function() {
        rs.resume();
    });
});

rs.on('end', function() {
    cleanUp();
});
以上給doSomething()函數加上了回調函數,因此我們可以在處理數據前暫停數據讀取,並在處理數據后繼續讀取數據。
此外,還可以為數據目標創建一個只寫數據流,例:
var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst);

rs.on('data', function(chunk) {
    ws.write(chunk);
});

rs.on('end', function(chunk) {
    ws.end():
});

以上代碼看起來就像是一個文件拷貝程序了,不過,依然存在寫入速度跟不上讀取速度,會導致緩存爆倉的問題,我們可以根據.write方法的返回值來判斷傳入的數據是寫入目標了,還是臨時放在了緩存了,並根據drain事件來判斷什么時候只寫數據流已經將緩存中的數據寫入目標,可以傳入下一個待寫數據了:

var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst);
rs.on('data', function(chunk) {
    if(ws.write(chunk) === false) {
        rs.pause();
    }
});

rs.on('end', function() {
    ws.end();
});

rs.on('drain', function() {
    rs.resume();
});

實現了數據從只讀數據流到只寫數據流的搬運,並包括了放爆倉控制,NodeJS直接提供了.pipe方法來做這件事,內部實現方式與上述代碼類似

File System(文件系統)

NodeJS通過內置fs模塊提供對文件的操作,fs模塊提供的API基本上可以分為以下三類:
文件屬性讀寫:
常用的有:fs.stat、fs.chmod、fs.chown等等
文件內容讀寫:
常用的有:fs.readFile、fs.readdir、fs.writeFile、fs.mkdir等等
底層文件操作:
常用的有:fs.open、fs.read、fs.write、fs.close等等
NodeJS最精華的異步IO模型在fs模塊里有着充分的體現,例如上面提到的API都通過回調函數傳遞結果,例:
fs.readFile(pathName, function(err, data) {
    if(err) {
        // Deal with error.
    } else {
        // Deal with data
    }
});
基本上所有fs模塊API回調參數都有兩個,第一個參數在有錯誤發生時等於異常對象,第二個參數始終用於返回API方法執行結果。
此外,fs模塊的所有異步API都有對應的同步版本,用於無法使用異步操作時,或同步操作更方便時。同步API除了方法名末尾多了一個Sync之外,異常對象的處理需要使用異常捕獲

Path(路徑)

操作文件時難免與文件路徑打交道,NodeJS提供了path內置模塊來簡化路徑相關操作,並提升代碼可讀性。

path.normalize

將傳入的路徑轉換為標准路徑,具體講的話,除了解析路徑中的.與..之外,還能去掉多余的斜杠,如果程序需要使用路徑作為某些數據的索引,但有允許用戶隨意輸入路徑時,就需要使用該方法保證路徑的唯一性,例:

var cache = {};
function store(key, value) {
    cache[path.normalize(key)] = value;
}
store('foo/bar', 1);
store('foo//baz//../bar', 2);
console.log(cache); // {'foo/bar': 2}

 

注意:標准化之后的路徑里的斜杠在Window系統下是\,而在linux系統下是/,若想保證任何系統都使用/作為路徑分隔符的話,需要.replace(/\\/g, '/')再替換下標准路徑

path.join

將傳入的多個路徑拼接為標准路徑,該方法可避免手工拼接字符串的繁瑣,並且能在不同系統下正確使用相應路徑分隔符。例:

path.join('foo/', 'baz/', '../bar'); // => "foo/bar"

path.extname

可獲取文件的擴展名,當我們需要根據不同文件擴展名做不同操作時,該方法就顯得很好用,例:

path.extname('foo/bar.js'); // => ".js"

遍歷目錄

遍歷目錄是操作文件時一個常見需求,例,寫一個程序用於查找指定目錄下的所有js文件時,就需要遍歷整個目錄
遞歸算法
遍歷目錄一般使用遞歸算法,否則就較難寫出簡潔的代碼
:使用遞歸算法雖然簡潔,但每次遞歸都會產生函數調用,在需要優先考慮性能時,需要把遞歸算法轉化為循環算法,以減少函數調用次數
遍歷算法
目錄是一個樹狀結構,遍歷時一般采用深度優先+先序遍歷算法
同步遍歷
結合算法,使用文件系統的同步API
異步遍歷
結合算法,使用文件系統的異步API,實現會變得復雜,但原理相同

文本編碼

常用的文本編碼有UTF8和GBK兩種,並且UTF8還可能帶有BOM,在讀取不同編碼的文本文件時,需要將文件內容轉換為JS使用的UTF8編碼字符串后才能正常處理

BOM的移除

BOM用於標記一個文件使用Unicode編碼,其本身是一個Unicode字符(“\uFEFF”),位於文本文件頭部,在不同Unicode編碼下,BOM字符對應的二進制字節如下:

因此我們可以根據文本文件頭幾個字節等於啥來判斷文件是否包含BOM,以及使用哪種Unicode編碼,但BOM並不屬於文件的一部分,需要去掉,否則在某些應用場景下會有問題。以下代碼實現了識別和去除UTF8 BOM的功能

function readText(pathName) {
    var bin = fs.readFileSync(pathName);
    if(bin[0] === 0xEF && bin[1] === 0xBB && bin[2] === 0xBF) {
        bin = bin.slice(3);
    }
    return bin.toString('utf-8');
}
注:
1、如果不是很在意性能,可以使用fs模塊的同步API,更容易理解
2、需要對文件讀寫做到字節級別的精細控制時,請使用fs模塊文件底層操作API
3、不要使用拼接字符串的方式來處理路徑,使用path模塊


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM