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。
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(); });
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(文件系統)
fs.readFile(pathName, function(err, data) { if(err) { // Deal with error. } else { // Deal with data } });
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"
遍歷目錄
文本編碼
常用的文本編碼有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'); }