在 NodeJS 中族购,所有與文件操作都是通過(guò) fs 核心模塊來(lái)實(shí)現(xiàn)的淳玩,包括文件目錄的創(chuàng)建旁瘫、刪除引颈、查詢以及文件的讀取和寫(xiě)入耕皮,在 fs 模塊中,所有的方法都分為同步和異步兩種實(shí)現(xiàn)蝙场,具有 sync 后綴的方法為同步方法凌停,不具有 sync 后綴的方法為異步方法。
1售滤、對(duì)文件操作
1.1 文件讀取
- (1)fs.readFile(path[, options], callback):異步讀取
- (2)fs.readFileSync(path[, options]):同步讀取
- (3)fs.createReadStream(path[, options]):通過(guò)文件流讀取罚拟,適合讀取大文件
const fs = require('fs');
const path = require('path');
// 同步讀取
try{
let data = fs.readFileSync(path.join(__dirname, "./test1.txt"), 'utf8');
console.log('文件內(nèi)容: ' + data);
}catch(err){
console.error('讀取文件出錯(cuò): ' + err.message);
}
// 異步讀取
fs.readFile(path.join(__dirname, "./test1.txt"), 'utf8', function(err, data){
if(err){
return console.error('讀取文件出錯(cuò): ' + err.message);
}
console.log('文件內(nèi)容: ' + data);
});
fs.createReadStream(path.join(__dirname, "./test1.txt"), 'utf8').on('data', function(chunk) {
console.log('讀取數(shù)據(jù): ' + chunk);
}).on('error', function(err){
console.log('出錯(cuò): ' + err.message);
}).on('end', function(){ // 沒(méi)有數(shù)據(jù)了
console.log('沒(méi)有數(shù)據(jù)了');
}).on('close', function(){ // 已經(jīng)關(guān)閉,不會(huì)再有事件拋出
console.log('已經(jīng)關(guān)閉');
});
// 結(jié)果如下:
// 文件內(nèi)容: 這是一段測(cè)試文件讀取的文本
// 讀取數(shù)據(jù): 這是一段測(cè)試文件讀取的文本
// 沒(méi)有數(shù)據(jù)了
// 文件內(nèi)容: 這是一段測(cè)試文件讀取的文本
// 已經(jīng)關(guān)閉
1.2 文件寫(xiě)入
1.2.1 文件寫(xiě)入
- fs.writeSync(fd, buffer[, offset[, length[, position]]])
- fs.writeFile(file, data[, options], callback)
在同一個(gè)文件上多次使用fs.writeFile()
且不等待回調(diào)是不安全的完箩。 對(duì)于這種情況赐俗,建議使用fs.createWriteStream()
。
const fs = require('fs');
const path = require('path');
// 同步寫(xiě)入
try{
fs.writeFileSync(path.join(__dirname, "./test1.txt"), '同步寫(xiě)入功能測(cè)試', 'utf8');
console.log(fs.readFileSync(path.join(__dirname, "./test1.txt"), 'utf8'));
}catch(err){
throw err;
}
// 異步寫(xiě)入
fs.writeFile(path.join(__dirname, "./test1.txt"),'異步寫(xiě)入功能測(cè)試', 'utf8', function(err, data){
if(err) throw err;
console.log(fs.readFileSync(path.join(__dirname, "./test1.txt"), 'utf8'));
});
- fs.createWriteStream(path[, options])
const fs = require('fs');
const path = require('path');
let writeStream = fs.createWriteStream(path.join(__dirname, "./test1.txt"),'utf8');
//已打開(kāi)要寫(xiě)入的文件事件
writeStream.on('open', (fd) => {
console.log('文件已打開(kāi):', fd); // 打開(kāi)一個(gè)新的文件,文件描述符會(huì)是3
});
//讀取文件發(fā)生錯(cuò)誤事件
writeStream.on('error', (err) => {
console.log('發(fā)生異常:', err);
});
//文件已經(jīng)就寫(xiě)入完成事件
writeStream.on('finish', () => {
console.log('寫(xiě)入已完成...');
console.log('讀取文件內(nèi)容:', fs.readFileSync(path.join(__dirname, "./test1.txt"), 'utf8'));
});
//文件關(guān)閉事件
writeStream.on('close', () => {
console.log('文件已關(guān)閉弊知!');
});
writeStream.write('文件流寫(xiě)入');
writeStream.end();
// 輸出結(jié)果:
// 文件已打開(kāi): 3
// 寫(xiě)入已完成...
// 讀取文件內(nèi)容: 文件流寫(xiě)入
// 文件已關(guān)閉阻逮!
以上三個(gè)方法,將數(shù)據(jù)寫(xiě)入到一個(gè)文件秩彤,如果文件已存在則覆蓋該文件叔扼。
1.2.2 文件追加寫(xiě)入
- fs.appendFile(file, data[, options], callback):異步追加
- fs.appendFileSync(path, data[, options]):同步追加
const fs = require('fs');
const path = require('path');
// 異步追加
fs.appendFile(path.join(__dirname, "./test1.txt"),'文件追加數(shù)據(jù)1', 'utf8', err => {
if(err) throw err;
console.log(fs.readFileSync(path.join(__dirname, "./test1.txt"), 'utf8'));
});
// 使用writeFile追加寫(xiě)入,將flag設(shè)置為'a'就可以了漫雷。
fs.writeFile(path.join(__dirname, "./test1.txt"), '文件追加數(shù)據(jù)2', {'flag': 'a'}, (err) => {
if(err) throw err;
console.log(fs.readFileSync(path.join(__dirname, "./test1.txt"), 'utf8'));
});
標(biāo)識(shí)位(flags)代表著對(duì)文件的操作方式瓜富,如可讀、可寫(xiě)降盹、即可讀又可寫(xiě)等等与柑,在下面用一張表來(lái)表示文件操作的標(biāo)識(shí)位和其對(duì)應(yīng)的含義。
符號(hào) | 含義 |
---|---|
r | 讀取文件蓄坏,如果文件不存在則拋出異常价捧。 |
r+ | 讀取并寫(xiě)入文件,如果文件不存在則拋出異常涡戳。 |
rs | 讀取并寫(xiě)入文件干旧,指示操作系統(tǒng)繞開(kāi)本地文件系統(tǒng)緩存。 |
w | 寫(xiě)入文件妹蔽,文件不存在會(huì)被創(chuàng)建,存在則清空后寫(xiě)入挠将。 |
wx | 寫(xiě)入文件胳岂,排它方式打開(kāi)。 |
w+ | 讀取并寫(xiě)入文件舔稀,文件不存在則創(chuàng)建文件乳丰,存在則清空后寫(xiě)入。 |
wx+ | 和 w+ 類似内贮,排他方式打開(kāi)产园。 |
a | 追加寫(xiě)入汞斧,文件不存在則創(chuàng)建文件。 |
ax | 與 a 類似什燕,排他方式打開(kāi)粘勒。 |
a+ | 讀取并追加寫(xiě)入,不存在則創(chuàng)建屎即。 |
ax+ | 與 a+ 類似庙睡,排他方式打開(kāi)。 |
1.2.3 文件拷貝寫(xiě)入
- fs.copyFile(src, dest[, flags], callback):異步拷貝
flags 是一個(gè)可選的整數(shù)技俐,指定拷貝操作的行為乘陪。 可以創(chuàng)建由兩個(gè)或更多個(gè)值按位或組成的掩碼(比如fs.constants.COPYFILE_EXCL
|fs.constants.COPYFILE_FICLONE
)。
const fs = require('fs');
const path = require('path');
const resource = path.join(__dirname, './test1.txt');
const target = path.join(__dirname, 'target.txt');
const { COPYFILE_EXCL } = fs.constants;
// 默認(rèn)情況下將創(chuàng)建或覆蓋目標(biāo)文件雕擂。
fs.copyFile(resource, target, (err) => {
if (err) throw err;
console.log('test1.txt已拷貝到target.txt中');
});
// 通過(guò)使用 COPYFILE_EXCL啡邑,如果目標(biāo)文件存在,則操作將失敗井赌。
fs.copyFile(resource, target, COPYFILE_EXCL, (err) => {
if (err) throw err;
});
符號(hào) | 含義 |
---|---|
fs.constants.COPYFILE_EXCL | 如果 dest 已存在谤逼,則拷貝操作將失敗。 |
fs.constants.COPYFILE_FICLONE | 拷貝操作將嘗試創(chuàng)建寫(xiě)時(shí)拷貝(copy-on-write)鏈接族展。如果平臺(tái)不支持寫(xiě)時(shí)拷貝森缠,則使用后備的拷貝機(jī)制。 |
fs.constants.COPYFILE_FICLONE_FORCE | 拷貝操作將嘗試創(chuàng)建寫(xiě)時(shí)拷貝鏈接仪缸。如果平臺(tái)不支持寫(xiě)時(shí)拷貝贵涵,則拷貝操作將失敗。 |
1.3 文件操作高級(jí)方法
1.3.1 打開(kāi)文件恰画,讀取文件宾茂,寫(xiě)入文件
const fs = require('fs');
const path = require('path');
/* 參數(shù)一表示文件路徑
* 參數(shù)二表示文件系統(tǒng)標(biāo)志,r+:打開(kāi)文件用于讀取和寫(xiě)入拴还。如果文件不存在跨晴,則出現(xiàn)異常。
* 參數(shù)三表示文件權(quán)限
* 參數(shù)四表示回調(diào)函數(shù)片林,err表示錯(cuò)誤端盆,fd表示文件描述符,是一個(gè)整型*/
fs.open(path.join(__dirname, "./test1.txt"), 'r+', 0o666, function (err, fd) {
let wbuf = Buffer.from('這是寫(xiě)入的數(shù)據(jù)');
/*參數(shù)一表示文件描述符
* 參數(shù)二表示寫(xiě)入數(shù)據(jù)的Buffer
* 參數(shù)三表示往Buffer中讀取的偏移量
* 參數(shù)四表示寫(xiě)入的字節(jié)數(shù)
* 參數(shù)五表示從文件中寫(xiě)入的位置费封,如果不等于數(shù)字焕妙,則從文件的當(dāng)前位置寫(xiě)入
* 參數(shù)六表示回調(diào)函數(shù),err表示錯(cuò)誤弓摘,written表示實(shí)際寫(xiě)入的字節(jié)數(shù)焚鹊,buffer表示寫(xiě)入數(shù)據(jù)的Buffer*/
fs.write(fd, wbuf, 0, 21, null, function (err, bytesLen, buffer) {
console.log(bytesLen); // 21
});
//創(chuàng)建一個(gè)3字節(jié)的Buffer,用來(lái)接收數(shù)據(jù)
let rbuf = Buffer.alloc(21);
/*參數(shù)一表示文件描述符
* 參數(shù)二表示接收數(shù)據(jù)的Buffer
* 參數(shù)三表示往Buffer中寫(xiě)入的偏移量
* 參數(shù)四表示讀取的字節(jié)數(shù)
* 參數(shù)五表示從文件中讀取的位置韧献,如果為null末患,則是文件的當(dāng)前位置讀取
* 參數(shù)六表示回調(diào)函數(shù)研叫,err表示錯(cuò)誤,bytesRead表示實(shí)際讀取的字節(jié)璧针,buffer表示接收數(shù)據(jù)的Buffer*/
fs.read(fd, rbuf, 0, 21, 0, function (err, bytesLen, buffer) {
console.log(rbuf.toString()); // 這是寫(xiě)入的數(shù)據(jù)
console.log(bytesLen); // 21
});
});
在 NodeJS 中嚷炉,每操作一個(gè)文件,文件描述符是遞增的陈莽,文件描述符一般從 3 開(kāi)始渤昌,因?yàn)榍懊嬗?0、1走搁、2 三個(gè)比較特殊的描述符独柑,分別代表 process.stdin
(標(biāo)準(zhǔn)輸入)、process.stdout
(標(biāo)準(zhǔn)輸出)和 process.stderr
(錯(cuò)誤輸出)私植。
1.3.2 同步磁盤(pán)緩存
const fs = require('fs');
const path = require('path');
// 0o666: 給文件所有者忌栅、群組、其他人 可讀可寫(xiě)不可執(zhí)行的權(quán)限
fs.open(path.join(__dirname, "./test1.txt"), 'w+', 0o666, function (err, fd) {
let task = [];
// 往文件中循環(huán)寫(xiě)入數(shù)據(jù)
for (let ix = 0; ix < 5; ix++) {
let data = Buffer.from(`數(shù)據(jù)${ix}`);
task.push(function () {
return new Promise((resolve, reject) => {
fs.write(fd, data, 0, data.length, null, (err, written, buffer) => {
if (err) {
reject(err);
} else {
resolve(buffer);
}
});
});
});
}
// 往文件中循環(huán)寫(xiě)入數(shù)據(jù)
Promise.all(task.map(fn => fn())).then(value => {
console.log("value:"+value.toString());
});
// 系統(tǒng)為了效率曲稼,在使用 write 方法向文件寫(xiě)入數(shù)據(jù)時(shí)
// 通常會(huì)放到一個(gè)緩沖區(qū)中索绪,當(dāng)緩沖區(qū)滿了后,系統(tǒng)就一次把數(shù)據(jù)寫(xiě)到文件贫悄。
// 當(dāng)寫(xiě)完數(shù)據(jù)后瑞驱,一般會(huì)強(qiáng)制刷新緩沖區(qū),讓數(shù)據(jù)寫(xiě)入到文件里窄坦,然后關(guān)閉文件唤反。
fs.fsync(fd, (err) => {
console.log("err1:"+err);
//關(guān)閉文件
fs.close(fd, function (err) {
console.log("err2:"+err);
})
});
});
1.3.3 大文件實(shí)現(xiàn) copy
// 如果是一個(gè)大文件一次性寫(xiě)入不現(xiàn)實(shí),所以需要多次讀取多次寫(xiě)入
const fs = require('fs');
const path = require('path');
function copy(src, dest, size = 16 * 1024, callback) {
// 打開(kāi)源文件
fs.open(src, 'r', (err, readFd) => {
// 打開(kāi)目標(biāo)文件
fs.open(dest, 'w', (err, writeFd) => {
let buf = Buffer.alloc(size);
let readed = 0; // 下次讀取文件的位置
let writed = 0; // 下次寫(xiě)入文件的位置
(function next() {
// 讀取
fs.read(readFd, buf, 0, size, readed, (err, bytesRead) => {
readed += bytesRead;
console.log("bytesRead:"+bytesRead);
// 如果都不到內(nèi)容關(guān)閉文件,并且不再執(zhí)行
if (!bytesRead) {
fs.close(readFd, err => console.log('關(guān)閉源文件'));
}
// 寫(xiě)入
fs.write(writeFd, buf, 0, bytesRead, writed, (err, bytesWritten) => {
// 如果沒(méi)有內(nèi)容了同步緩存鸭津,并關(guān)閉文件后執(zhí)行回調(diào)
if (!bytesWritten) {
return fs.fsync(writeFd, err => {
fs.close(writeFd, err => !err && callback(writeFd));
});
}
writed += bytesWritten;
// 繼續(xù)讀取彤侍、寫(xiě)入
next();
});
});
})();
});
});
}
/* test1.txt內(nèi)容:數(shù)據(jù)0數(shù)據(jù)1數(shù)據(jù)2數(shù)據(jù)3數(shù)據(jù)4 size=7,每次讀取寫(xiě)入7個(gè)字節(jié)*/
copy(path.join(__dirname, "./test1.txt"), path.join(__dirname, "./target.txt"), size = 7,
(data) => {
console.log("文件copy結(jié)束");
});
/* 結(jié)果如下,文件讀取寫(xiě)入五次逆趋,第六次的時(shí)候沒(méi)有讀取到任何內(nèi)容結(jié)束
* bytesRead:7
* bytesRead:7
* bytesRead:7
* bytesRead:7
* bytesRead:7
* bytesRead:0
* 關(guān)閉源文件
* 文件copy結(jié)束*/
2盏阶、對(duì)目錄操作
2.1創(chuàng)建、刪除目錄
- fs.mkdir(path[, options], callback)
- fs.rmdir(path, callback):異步刪除闻书,不能刪除非空目錄
- fs.statSync(path[, options]):檢查文件或者目錄屬性
const fs = require('fs');
const path = require('path');
//創(chuàng)建目錄名斟,默認(rèn)情況下不支持遞歸創(chuàng)建目錄
fs.mkdir('./e', function (err) {
console.log(err);
});
//通過(guò)設(shè)置參數(shù)二中的recursive為true,則可以遞歸創(chuàng)建目錄
fs.mkdir('./a/b/c', {'recursive': true}, function (err) {
console.log(err);
});
//rmdir無(wú)法刪除非空目錄
fs.rmdir('./e', function (err) {
console.log(err);
});
// 遞歸刪除不為空的文件夾
function delDir(dir){
let files = [];
if(fs.existsSync(dir)){
files = fs.readdirSync(dir);
files.forEach((file, index) => {
let curPath = path.join(dir, file);
if(fs.statSync(curPath).isDirectory()){
delDir(curPath); //遞歸刪除文件夾
} else {
fs.unlinkSync(curPath); //刪除文件
}
});
fs.rmdirSync(dir);
}
}
delDir('./a');
2.2 讀取目錄下的所有文件
- fs.readdir(path[, options], callback):異步方式讀取文件
- fs.stat(path[, options], callback):異步獲取文件目錄屬性
const fs = require('fs');
const path = require('path');
// readdir讀取目錄下所有文件
fs.readdir(__dirname, function (err, files) {
console.log(files);
});
// 遞歸的讀取一個(gè)目錄所有文件
function readDir(dir) {
// 文件目錄的 Stats 對(duì)象存儲(chǔ)著關(guān)于這個(gè)文件或文件夾的一些重要信息魄眉,
// 如創(chuàng)建時(shí)間砰盐、修改的時(shí)間、文章所占字節(jié)和判斷文件類型的多個(gè)方法等等杆融。
fs.stat(dir, function (err, stats) {
if (stats.isDirectory()) {
console.log(dir);
fs.readdir(dir, function (err, files) {
files.map(value => {
let cur = path.join(dir, value);
fs.stat(cur, function (err, stats) {
if (stats.isDirectory()) {
readDir(cur);
} else {
console.log(cur);
}
});
});
});
} else {
console.log(dir);
}
});
}
readDir('./example');
參考文件:
node.js高級(jí)編程#第七章#第九章
https://www.cnblogs.com/jkko123/p/10231153.html
https://www.overtaking.top/2018/06/30/20180630172601/
https://blog.csdn.net/adley_app/article/details/83010257