【原创】Node核心API(三)fs

在 NodeJS 中,所有与文件操作都是通过 fs 核心模块来实现的,包括文件目录的创建、删除、查询以及文件的读取和写入,在 fs 模块中,所有的方法都分为同步和异步两种实现,具有 sync 后缀的方法为同步方法,不具有 sync 后缀的方法为异步方法。

1、对文件操作

1.1 文件读取

  • (1)fs.readFile(path[, options], callback):异步读取
  • (2)fs.readFileSync(path[, options]):同步读取
  • (3)fs.createReadStream(path[, options]):通过文件流读取,适合读取大文件
const fs = require('fs');
const path = require('path');
// 同步读取
try{
    let data = fs.readFileSync(path.join(__dirname, "./test1.txt"), 'utf8');
    console.log('文件内容: ' + data);
}catch(err){
    console.error('读取文件出错: ' + err.message);
}
// 异步读取
fs.readFile(path.join(__dirname, "./test1.txt"), 'utf8', function(err, data){
    if(err){
        return console.error('读取文件出错: ' + err.message);
    }
    console.log('文件内容: ' + data);
});

fs.createReadStream(path.join(__dirname, "./test1.txt"), 'utf8').on('data', function(chunk) {
        console.log('读取数据: ' + chunk);
    }).on('error', function(err){
        console.log('出错: ' + err.message);
    }).on('end', function(){  // 没有数据了
        console.log('没有数据了');
    }).on('close', function(){  // 已经关闭,不会再有事件抛出
        console.log('已经关闭');
    });
// 结果如下:
// 文件内容: 这是一段测试文件读取的文本
// 读取数据: 这是一段测试文件读取的文本
// 没有数据了
// 文件内容: 这是一段测试文件读取的文本
// 已经关闭

1.2 文件写入

1.2.1 文件写入
  • fs.writeSync(fd, buffer[, offset[, length[, position]]])
  • fs.writeFile(file, data[, options], callback)
    在同一个文件上多次使用 fs.writeFile() 且不等待回调是不安全的。 对于这种情况,建议使用 fs.createWriteStream()
const fs = require('fs');
const path = require('path');
// 同步写入
try{
    fs.writeFileSync(path.join(__dirname, "./test1.txt"), '同步写入功能测试', 'utf8');
    console.log(fs.readFileSync(path.join(__dirname, "./test1.txt"), 'utf8'));
}catch(err){
    throw err;
}
// 异步写入
fs.writeFile(path.join(__dirname, "./test1.txt"),'异步写入功能测试', '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');
//已打开要写入的文件事件
writeStream.on('open', (fd) => {
    console.log('文件已打开:', fd); // 打开一个新的文件,文件描述符会是3
});
//读取文件发生错误事件
writeStream.on('error', (err) => {
    console.log('发生异常:', err);
});
//文件已经就写入完成事件
writeStream.on('finish', () => {
    console.log('写入已完成...');
    console.log('读取文件内容:', fs.readFileSync(path.join(__dirname, "./test1.txt"), 'utf8'));
});
//文件关闭事件
writeStream.on('close', () => {
    console.log('文件已关闭!');
});
writeStream.write('文件流写入');
writeStream.end();
// 输出结果:
// 文件已打开: 3
// 写入已完成...
// 读取文件内容: 文件流写入
// 文件已关闭!

以上三个方法,将数据写入到一个文件,如果文件已存在则覆盖该文件。

1.2.2 文件追加写入
  • 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"),'文件追加数据1', 'utf8', err => {
    if(err) throw err;
    console.log(fs.readFileSync(path.join(__dirname, "./test1.txt"), 'utf8'));
});
// 使用writeFile追加写入,将flag设置为'a'就可以了。
fs.writeFile(path.join(__dirname, "./test1.txt"), '文件追加数据2', {'flag': 'a'}, (err) => {
    if(err) throw err;
    console.log(fs.readFileSync(path.join(__dirname, "./test1.txt"), 'utf8'));
});

标识位(flags)代表着对文件的操作方式,如可读、可写、即可读又可写等等,在下面用一张表来表示文件操作的标识位和其对应的含义。

符号 含义
r 读取文件,如果文件不存在则抛出异常。
r+ 读取并写入文件,如果文件不存在则抛出异常。
rs 读取并写入文件,指示操作系统绕开本地文件系统缓存。
w 写入文件,文件不存在会被创建,存在则清空后写入。
wx 写入文件,排它方式打开。
w+ 读取并写入文件,文件不存在则创建文件,存在则清空后写入。
wx+ 和 w+ 类似,排他方式打开。
a 追加写入,文件不存在则创建文件。
ax 与 a 类似,排他方式打开。
a+ 读取并追加写入,不存在则创建。
ax+ 与 a+ 类似,排他方式打开。
1.2.3 文件拷贝写入
  • fs.copyFile(src, dest[, flags], callback):异步拷贝
    flags 是一个可选的整数,指定拷贝操作的行为。 可以创建由两个或更多个值按位或组成的掩码(比如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;
// 默认情况下将创建或覆盖目标文件。
fs.copyFile(resource, target, (err) => {
    if (err) throw err;
    console.log('test1.txt已拷贝到target.txt中');
});

// 通过使用 COPYFILE_EXCL,如果目标文件存在,则操作将失败。
fs.copyFile(resource, target, COPYFILE_EXCL, (err) => {
    if (err) throw err;
});
符号 含义
fs.constants.COPYFILE_EXCL 如果 dest 已存在,则拷贝操作将失败。
fs.constants.COPYFILE_FICLONE 拷贝操作将尝试创建写时拷贝(copy-on-write)链接。如果平台不支持写时拷贝,则使用后备的拷贝机制。
fs.constants.COPYFILE_FICLONE_FORCE 拷贝操作将尝试创建写时拷贝链接。如果平台不支持写时拷贝,则拷贝操作将失败。

1.3 文件操作高级方法

1.3.1 打开文件,读取文件,写入文件
const fs = require('fs');
const path = require('path');

/* 参数一表示文件路径
*  参数二表示文件系统标志,r+:打开文件用于读取和写入。如果文件不存在,则出现异常。
*  参数三表示文件权限
*  参数四表示回调函数,err表示错误,fd表示文件描述符,是一个整型*/
fs.open(path.join(__dirname, "./test1.txt"), 'r+', 0o666, function (err, fd) {
    let wbuf = Buffer.from('这是写入的数据');
    /*参数一表示文件描述符
    * 参数二表示写入数据的Buffer
    * 参数三表示往Buffer中读取的偏移量
    * 参数四表示写入的字节数
    * 参数五表示从文件中写入的位置,如果不等于数字,则从文件的当前位置写入
    * 参数六表示回调函数,err表示错误,written表示实际写入的字节数,buffer表示写入数据的Buffer*/
    fs.write(fd, wbuf, 0, 21, null, function (err, bytesLen, buffer) {
        console.log(bytesLen); // 21
    });

    //创建一个3字节的Buffer,用来接收数据
    let rbuf = Buffer.alloc(21);
    /*参数一表示文件描述符
    * 参数二表示接收数据的Buffer
    * 参数三表示往Buffer中写入的偏移量
    * 参数四表示读取的字节数
    * 参数五表示从文件中读取的位置,如果为null,则是文件的当前位置读取
    * 参数六表示回调函数,err表示错误,bytesRead表示实际读取的字节,buffer表示接收数据的Buffer*/
    fs.read(fd, rbuf, 0, 21, 0, function (err, bytesLen, buffer) {
        console.log(rbuf.toString()); // 这是写入的数据
        console.log(bytesLen); // 21
    });
}); 

在 NodeJS 中,每操作一个文件,文件描述符是递增的,文件描述符一般从 3 开始,因为前面有 0、1、2 三个比较特殊的描述符,分别代表 process.stdin(标准输入)、process.stdout(标准输出)和 process.stderr(错误输出)。

1.3.2 同步磁盘缓存
const fs = require('fs');
const path = require('path');
// 0o666: 给文件所有者、群组、其他人 可读可写不可执行的权限
fs.open(path.join(__dirname, "./test1.txt"), 'w+', 0o666, function (err, fd) {
    let task = [];
    // 往文件中循环写入数据
    for (let ix = 0; ix < 5; ix++) {
        let data = Buffer.from(`数据${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);
                    }
                });
            });
        });
    }

    // 往文件中循环写入数据
    Promise.all(task.map(fn => fn())).then(value => {
        console.log("value:"+value.toString());
    });
    // 系统为了效率,在使用 write 方法向文件写入数据时
    // 通常会放到一个缓冲区中,当缓冲区满了后,系统就一次把数据写到文件。
    // 当写完数据后,一般会强制刷新缓冲区,让数据写入到文件里,然后关闭文件。
    fs.fsync(fd, (err) => {
        console.log("err1:"+err);
        //关闭文件
        fs.close(fd, function (err) {
            console.log("err2:"+err);
        })
    });
});
1.3.3 大文件实现 copy
// 如果是一个大文件一次性写入不现实,所以需要多次读取多次写入
const fs = require('fs');
const path = require('path');
function copy(src, dest, size = 16 * 1024, callback) {
    // 打开源文件
    fs.open(src, 'r', (err, readFd) => {
        // 打开目标文件
        fs.open(dest, 'w', (err, writeFd) => {
            let buf = Buffer.alloc(size);
            let readed = 0; // 下次读取文件的位置
            let writed = 0; // 下次写入文件的位置

            (function next() {
                // 读取
                fs.read(readFd, buf, 0, size, readed, (err, bytesRead) => {
                    readed += bytesRead;
                    console.log("bytesRead:"+bytesRead);

                    // 如果都不到内容关闭文件,并且不再执行
                    if (!bytesRead) {
                        fs.close(readFd, err => console.log('关闭源文件'));
                    }

                    // 写入
                    fs.write(writeFd, buf, 0, bytesRead, writed, (err, bytesWritten) => {
                        // 如果没有内容了同步缓存,并关闭文件后执行回调
                        if (!bytesWritten) {
                            return fs.fsync(writeFd, err => {
                                fs.close(writeFd, err => !err && callback(writeFd));
                            });
                        }
                        writed += bytesWritten;
                        // 继续读取、写入
                        next();
                    });
                });
            })();
        });
    });
}
/* test1.txt内容:数据0数据1数据2数据3数据4  size=7,每次读取写入7个字节*/
copy(path.join(__dirname, "./test1.txt"), path.join(__dirname, "./target.txt"), size = 7,
    (data) => {
    console.log("文件copy结束");
});
/* 结果如下,文件读取写入五次,第六次的时候没有读取到任何内容结束
* bytesRead:7
* bytesRead:7
* bytesRead:7
* bytesRead:7
* bytesRead:7
* bytesRead:0
* 关闭源文件
* 文件copy结束*/

2、对目录操作

2.1创建、删除目录

  • fs.mkdir(path[, options], callback)
  • fs.rmdir(path, callback):异步删除,不能删除非空目录
  • fs.statSync(path[, options]):检查文件或者目录属性
const fs = require('fs');
const path = require('path');

//创建目录,默认情况下不支持递归创建目录
fs.mkdir('./e', function (err) {
    console.log(err);
});

//通过设置参数二中的recursive为true,则可以递归创建目录
fs.mkdir('./a/b/c', {'recursive': true}, function (err) {
    console.log(err);
});

//rmdir无法删除非空目录
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);
});

// 递归的读取一个目录所有文件
function readDir(dir) {
    // 文件目录的 Stats 对象存储着关于这个文件或文件夹的一些重要信息,
    // 如创建时间、修改的时间、文章所占字节和判断文件类型的多个方法等等。
    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高级编程#第七章#第九章
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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容