本文为 Node.js 系列笔记第二篇。文章参考:nodejs 教程 —— 大地;《深入浅出 Node.js》;阮一峰 nodejs 博客; 添加链接描述
fs 是 filesystem 的缩写,该模块主要用于对系统文件及目录进行读写操作,它是 Node 的内置模块。下面介绍一下相关的常用 API 。
检测是文件还是目录。示例如下:
const fs = require('fs');
fs.stat('./html', (err, stats) => {
if (err) {
console.log(err);
return;
}
console.log(`是否为文件:${stats.isFile()}`); // 是否为文件:false
console.log(`是否为目录:${stats.isDirectory()}`); // 否为目录:true
})
以异步的方式用于创建目录。mkdir 接受三个参数。
fs.mkdir(path, [mode], [callback(err)])
path
:将创建的目录路径mode
:可选。目录权限(读写权限),默认0777callback
:可选。回调函数,传递异常参数err const fs = require('fs');
fs.mkdir('./css', (err) => {
if (err) {
console.log(err);
return;
}
console.log('创建成功'); // 创建成功
})
以异步的方式创建文件并将 data 写入文件,文件已存在的情况下,原内容将被替换。
fs.writeFile(filename, data, [options], [callback(err)])
filename
:文件名称
data
(String | Buffer) :将要写入的内容,可以使字符串或 buffer 数据。
options
(Object):可选。option 数组对象,包含:
1)encoding
:可选。默认 ‘utf8′,当 data 使 buffer 时,该值应该为 ignored。
2)mode
:文件读写权限,默认值 438
3)flag
:默认值 ‘w’
callback
:可选。回调函数,传递一个异常参数 err。
const fs = require('fs');
fs.writeFile('./html/index.html', 'Hello World', (err) => {
if (err) {
console.log(err);
return;
}
console.log('写入文件成功'); // 写入文件成功
})
将 data 插入到文件里,如果文件不存在会自动创建。与 fs.writeFile
的区别是,如果文件存在原内容不会被覆盖,而是进行追加。
fs.appendFile(filename, data, [options], callback)
const fs = require('fs');
fs.appendFile('./html/index.html', '\nnode.js', (err) => {
if (err) {
console.log(err);
return;
}
console.log('appendFile 成功');
})
以异步的方式读取文件内容。不置顶内容编码的情况下,将以 buffer 的格式输出,如:
fs.readFile(filename, [encoding], [callback(err,data)])
const fs = require('fs');
fs.readFile('./html/index.html', (err, data) => {
if (err) {
console.log(err);
return;
}
console.log(data);
// 打印
});
可以利用 toString()
将 Buffer 类型转换为 String:
console.log(data.toString());
// Hello World
// node.js
以异步的方式读取文件目录。
fs.readdir(path, [callback(err,files)])
path
:目录路径。callback
:可选。回调函数,传递两个参数 err 和 files(string[]),files 是一个包含 “ 指定目录下所有文件名称的” 数组。 const fs = require('fs');
fs.readdir('./day04', (err, files) => {
if (err) {
console.log(err);
return;
}
console.log(files); // [ 'index.html' ]
})
修改文件名称,可更改文件的存放路径。
fs.rename(oldPath, newPath, [callback(err)])
oldPath
:原路径。newPath
:新路径。callback
:回调函数。传递一个 err 异常参数。 const fs = require('fs');
fs.rename('./css/a.css', './css/index.css', (err) => {
if (err) {
console.log(err);
return;
}
console.log('更改成功'); // 更改成功
})
这里是并没有改变文件路径而只是改变了文件名称,如果想要改变文件路径,只需要将新路径改变为其他文件夹下即可,如 './html/index.css'
。
以异步的方式删除文件目录。
fs.rmdir(path, [callback(err)])
const fs = require('fs');
fs.rmdir('./css', (err) => {
if (err) {
console.log(err); // 保错:directory not empty
return;
}
console.log('删除目录成功');
})
上例报错:directory not empty
,说明:当目录中还有其他文件时不能直接删除目录。
删除文件操作。
fs.unlink(path, [callback(err)])
const fs = require('fs');
fs.unlink('./css/index.css', (err) => {
if (err) {
console.log(err);
return;
}
console.log('删除文件成功'); // 删除文件成功
})
需求:对于如下图所示文件夹 wwwroot,找出 wwwroot 目录下面所有的目录(css、img、js),并放到数组中。
当然,我们首先想到的是利用 for 循环遍历调用,判断是否为目录。实现代码(错误写法)如下所示:
const fs = require('fs');
var path = './wwwroot';
var dirArr = [];
fs.readdir(path, (err, data) => {
if (err) {
console.log(err);
return;
}
console.log(data); // [ 'book.txt', 'css', 'img', 'index.html', 'js' ]
for (var i = 0; i < data.length; i++) {
fs.stat(path + '/' + data[i], (error, stats) => {
if (stats.isDirectory()) {
dirArr.push(data[i]);
}
})
}
console.log(dirArr); // []
})
通过打印结果可以看出,这是有问题的。因为 fs.stat
是异步方法,其回调函数内容会放到任务队列中等待执行,当主程序执行栈中任务执行过后,才会被执行。
也就是说,console.log(dirArr)
会被先执行,之后才会执行 fs.stat
的回调函数。
为了解决这个问题,可以使用闭包递归执行
fs.readdir(path, (err, data) => {
if (err) {
console.log(err);
return;
}
console.log(data); // [ 'book.txt', 'css', 'img', 'index.html', 'js' ]
(function getDir(i) {
if (i == data.length) {
console.log(dirArr); // [ 'css', 'img', 'js' ]
return;
}
fs.stat(path + '/' + data[i], (error, stats) => {
if (stats.isDirectory()) {
dirArr.push(data[i]);
}
getDir(i + 1);
})
})(0)
})
但是这段代码看起来还是那么复杂,可行执行较差。除此之外,我们可以使用 Node 中新特性 async await
来解决此方法,这里暂时不先介绍,感兴趣的可参考下文。
async/await 是一个期待已久的 JavaScript 特性,让我们更好的理解使用异步函数。它建立在 Promises上,并且与所有现有的基于 Promise 的 API 兼容。
then
。await
就会立即返回一个 pending 状态的 Promise 对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行。下面说一下 async 和 await 的语法。
async function name([param]) {
statements
}
几点说明
async
函数内部的异步操作执行完,才会执行 then
方法指定的回调函数await
举个例子
async function test() {
return 'Promise';
}
console.log(test()); // Promise { 'Promise' }
他几点说明
await
表达式会暂停当前 async function
的执行,等待 Promise 处理完成。resolve
函数参数作为 await
表达式的值,继续执行 async function
。await
表达式会把 Promise 的异常原因抛出。举个例子
async function test() {
return 'Promise';
}
async function main() {
var data = await test();
console.log(data);
}
main(); // 打印 Promise
从外部获取异步方法里面的数据。代码如下:
function test() {
return new Promise((resolve, reject) => {
setTimeout(() => {
var name = '张三';
resolve(name);
}, 1000);
})
}
async function main() {
var data = await test();
// 等待获取 test() 中 Promise 处理结果,也就是等待获取 resolve 返回的参数 value
console.log(data);
}
main(); // 打印 张三
可以利用 async/await 对上一节遗留的问题进行改善,问题如下:
需求:对于如下图所示文件夹 wwwroot,找出 wwwroot 目录下面所有的目录(css、img、js),并放到数组中。
上节最后使用的是递归利用闭包解决了异步的问题,这里我们可以使用 async / await 将异步操作变为同步以此解决问题。见下面代码:
const fs = require('fs');
async function isDir(path) {
return new Promise((resolve, reject) => {
// 判断文件还是目录
fs.stat(path, (error, stats) => {
if (error) {
console.log(error);
reject(error);
}
if (stats.isDirectory()) {
resolve(true);
} else {
resolve(false);
}
})
})
}
function main() {
var path = './wwwroot';
var dirArr = [];
// 注意 await 应该只能在 async function 内部使用
fs.readdir(path, async (err, data) => {
if (err) {
console.log(err);
return;
}
for (var i = 0; i < data.length; i++) {
if (await isDir(path + '/' + data[i])) { // 利用 await 将异步操作改为同步
dirArr.push(data[i]);
}
}
console.log(dirArr);
})
}
main();
createReadStream
方法往往用于打开大型的文本文件,创建一个读取操作的数据流。所谓大型文本文件,指的是文本文件的体积很大,读取操作的缓存装不下,只能分成几次发送,每次发送会触发一个data
事件,发送结束会触发 end
事件。
const fs = require('fs');
var readStream = fs.createReadStream('./data/input.txt');
var count = 0;
var str = '';
readStream.on('data', (data) => {
str += data;
count++;
})
readStream.on('end', () => {
console.log(str);
console.log(count);
})
readStream.on('error', (err) => {
console.log(err);
})
createWriteStream
方法创建一个写入数据流对象,该对象的 write
方法用于写入数据,end
方法用于结束写入操作。
var fs = require('fs');
var str = '';
for (var i = 0; i < 500; i++) {
str += '太阳当空照,花儿对我笑\n';
}
var writeStream = fs.createWriteStream('./data/output.txt');
writeStream.write(str);
// 标记写入完成
writeStream.end();
writeStream.on('finish', () => {
console.log('写入完成');
})
注意:必须利用 writeStream.end()
标记文件结尾后,'finish'
事件才能被执行。
管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。利用 createWriteStream
方法和 createReadStream
方法配合,可以实现拷贝大型文件。
如下代码,拷贝一个图片 nodejs.png。
const fs = require('fs');
var readStream = fs.createReadStream('./nodejs.png');
var writeStream = fs.createWriteStream('./data/nodejs.png');
readStream.pipe(writeStream);