Node知識體系之文件系統

今天我們主要來介紹下Node體系中的文件系統。

Node知識體系之文件系統

fs 模塊交互的幾種方式

  • POSIX 文件 I/O
  • 文件流
  • 批量文件 I/O
  • 文件監控

POSIX 文件系統

Node知識體系之文件系統

Node知識體系之文件系統

代碼示例:

const fs = require('fs');
const assert = require('assert');
const fd = fs.openSync('./file.txt', 'w+');
const writeBuf = new Buffer('some data to write');
fs.writeSync(fd, writeBuf, 0, writeBuf.length, 0);
const readBuf = new Buffer(writeBuf.length);
fs.readSync(fd, readBuf, 0, writeBuf.length, 0);
assert.equal(writeBuf.toString(), readBuf.toString());
fs.closeSync(fd);

讀寫流

const fs = require('fs');
const readable = fs.createReadStream('./original.txt');
const writeable = fs.createWriteStream('./copy.txt');

readable.pipe(writeable);

文件監控

fs.watchFile 比 fs.watch 低效,但更好用。

同步讀取與 require

同步 fs 的方法應該在第一次初始化應用的時候使用。

const fs = require('fs');
const config = JSON.parse(fs.readFileSync('./config.json').toString());
init(config);

require:

const config = require('./config.json);
init(config);
  • 模塊會被全局緩衝,其他文件也加載並修改,會影響到整個系統加載了此文件的模塊
  • 可以通過 Object.freeze 來凍結一個對象

文件描述

文件描述是在操作系統中管理的在進程中打開文件所關聯的一些數字或者索引。操作系統通過指派一個唯一的整數給每個打開的文件用來查看關於這個文件。

Node知識體系之文件系統

console.log('log') 是 process.stdout.write('log') 的語法糖。

一個文件描述是 open 以及 openSync 方法調用返回的一個數字

const fd = fs.openSync('myfile', 'a');
console.log(typeof fd === 'number'); // true

文件鎖

協同多個進程同時訪問一個文件,保證文件的完整性以及數據不能丟失:

  • 強制鎖(在內核級別執行)
  • 諮詢鎖(非強制,只在涉及到進程訂閱了相同的鎖機制)
  • node-fs-ext 通過 flock 鎖住一個文件
  • 使用鎖文件
  • 進程 A 嘗試創建一個鎖文件,並且成功了
  • 進程 A 已經獲得了這個鎖,可以修改共享的資源
  • 進程 B 嘗試創建一個鎖文件,但失敗了,無法修改共享的資源

Node 實現鎖文件

  • 使用獨佔標記創建鎖文件
  • 使用 mkdir 創建鎖文件

獨佔標記

// 所有需要打開文件的方法,fs.writeFile、fs.createWriteStream、fs.open 都有一個 x 標記
// 這個文件應該已獨佔打開,若這個文件存在,文件不能被打開
fs.open('config.lock', 'wx', (err) => {
if (err) { return console.err(err); }
});
// 最好將當前進程號寫進文件鎖中
// 當有異常的時候就知道最後這個鎖的進程
fs.writeFile(
'config.lock',
process.pid,
{ flogs: 'wx' },
(err) => {
if (err) { return console.error(err) };
},
);

mkdir 文件鎖

獨佔標記有個問題,可能有些系統不能識別 0_EXCL 標記。另一個方案是把鎖文件換成一個目錄,PID 可以寫入目錄中的一個文件。

fs.mkidr('config.lock', (err) => {
if (err) { return console.error(err); }
fs.writeFile(`/config.lock/${process.pid}`, (err) => {

if (err) { return console.error(err); }
});
});

lock 模塊實現

const fs = require('fs');
const lockDir = 'config.lock';
let hasLock = false;
exports.lock = function (cb) { // 獲取鎖
if (hasLock) { return cb(); } // 已經獲取了一個鎖
fs.mkdir(lockDir, function (err) {
if (err) { return cb(err); } // 無法創建鎖
fs.writeFile(lockDir + '/' + process.pid, function (err) { // 把 PID寫入到目錄中以便調試
if (err) { console.error(err); } // 無法寫入 PID,繼續運行
hasLock = true; // 鎖創建了
return cb();
});
});
};
exports.unlock = function (cb) { // 解鎖方法
if (!hasLock) { return cb(); } // 如果沒有需要解開的鎖
fs.unlink(lockDir + '/' + process.pid, function (err) {
if (err) { return cb(err); }
fs.rmdir(lockDir, function (err) {
if (err) return cb(err);
hasLock = false;
cb();
});
});
};
process.on('exit', function () {
if (hasLock) {
fs.unlinkSync(lockDir + '/' + process.pid); // 如果還有鎖,在退出之前同步刪除掉
fs.rmdirSync(lockDir);
console.log('removed lock');
}
});

遞歸文件操作

推薦一個線上庫:mkdirp

遞歸:要解決我們的問題就要先解決更小的相同的問題。

dir-a
├── dir-b
│ ├── dir-c
│ │ ├── dir-d
│ │ │ └── file-e.png
│ │ └── file-e.png
│ ├── file-c.js
│ └── file-d.txt
├── file-a.js
└── file-b.txt

查找模塊:find /asset/dir-a -name="file.*"

[
'dir-a/dir-b/dir-c/dir-d/file-e.png',
'dir-a/dir-b/dir-c/file-e.png',
'dir-a/dir-b/file-c.js',
'dir-a/dir-b/file-d.txt',
'dir-a/file-a.js',
'dir-a/file-b.txt',
]

const fs = require('fs');
const join = require('path').join;
// 同步查找
exports.findSync = function (nameRe, startPath) {
const results = [];
function finder(path) {
const files = fs.readdirSync(path);
for (let i = 0; i < files.length; i++) {
const fpath = join(path, files[i]);
const stats = fs.statSync(fpath);
if (stats.isDirectory()) { finder(fpath); }
if (stats.isFile() && nameRe.test(files[i])) {
results.push(fpath);
}
}
}
finder(startPath);

return results;
};
// 異步查找
exports.find = function (nameRe, startPath, cb) { // cb 可以傳入 console.log,靈活
const results = [];
let asyncOps = 0; // 2
function finder(path) {
asyncOps++;
fs.readdir(path, function (er, files) {
if (er) { return cb(er); }
files.forEach(function (file) {
const fpath = join(path, file);
asyncOps++;
fs.stat(fpath, function (er, stats) {
if (er) { return cb(er); }
if (stats.isDirectory()) finder(fpath);
if (stats.isFile() && nameRe.test(file)) {
results.push(fpath);
}
asyncOps--;
if (asyncOps == 0) {
cb(null, results);
}
});
});
asyncOps--;
if (asyncOps == 0) {
cb(null, results);
}
});
}
finder(startPath);
};
console.log(exports.findSync(/file.*/, `${__dirname}/dir-a`));
console.log(exports.find(/file.*/, `${__dirname}/dir-a`, console.log));

監視文件和文件夾

想要監聽一個文件或者目錄,並在文件更改後執行一個動作。

const fs = require('fs');
fs.watch('./watchdir', console.log); // 穩定且快
fs.watchFile('./watchdir', console.log); // 跨平臺

逐行地讀取文件流

const fs = require('fs');
const readline = require('readline');
const rl = readline.createInterface({
input: fs.createReadStream('/etc/hosts'),
crlfDelay: Infinity
});
rl.on('line', (line) => {
console.log(`cc ${line}`);
const extract = line.match(/(\\d+\\.\\d+\\.\\d+\\.\\d+) (.*)/);
});


分享到:


相關文章: