【原創(chuàng)】Node核心API(三)fs

在 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市霜运,隨后出現(xiàn)的幾起案子脾歇,更是在濱河造成了極大的恐慌蒋腮,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藕各,死亡現(xiàn)場(chǎng)離奇詭異池摧,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)激况,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)作彤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人乌逐,你說(shuō)我怎么就攤上這事竭讳。” “怎么了浙踢?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵绢慢,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我洛波,道長(zhǎng)胰舆,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任蹬挤,我火速辦了婚禮缚窿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘焰扳。我一直安慰自己倦零,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布蓝翰。 她就那樣靜靜地躺著光绕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪畜份。 梳的紋絲不亂的頭發(fā)上诞帐,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音爆雹,去河邊找鬼停蕉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛钙态,可吹牛的內(nèi)容都是我干的慧起。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼册倒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蚓挤!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤灿意,失蹤者是張志新(化名)和其女友劉穎估灿,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體缤剧,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馅袁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荒辕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汗销。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖抵窒,靈堂內(nèi)的尸體忽然破棺而出弛针,到底是詐尸還是另有隱情,我是刑警寧澤估脆,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布钦奋,位于F島的核電站,受9級(jí)特大地震影響疙赠,放射性物質(zhì)發(fā)生泄漏付材。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一圃阳、第九天 我趴在偏房一處隱蔽的房頂上張望厌衔。 院中可真熱鬧,春花似錦捍岳、人聲如沸富寿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)页徐。三九已至,卻和暖如春银萍,著一層夾襖步出監(jiān)牢的瞬間变勇,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工贴唇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搀绣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓戳气,卻偏偏與公主長(zhǎng)得像链患,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瓶您,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容