文件系统概述
任何一门编程语言,尤其是后端的语言,都提供了文件系统的相关操作。
让前端觉得如获神器的不是NodeJS能做网络编程,而是NodeJS能够操作文件。小至文件查找,大至代码编译,几乎没有一个前端工具不操作文件。换个角度讲,几乎也只需要一些数据处理逻辑,再加上一些文件操作,就能够编写出大多数前端工具
文件,可以是任何的文件,包括文档、图片、电影、音频等文件。
- 百度云盘 文件的上传和下载
- 在开发的过程中,经常使用的一些构建工具,也是对文件进行相关的操作。比如npm、 webpack、vue-cli、gulp,都是对文件的相关操作。
所以说,文件操作是编程领域中非常重要的一个方面。浏览器端的js是不允许操作文件,基于安全考虑。但是服务端必须要提供对文件操作的支持,因为服务端是安全的。
在node.js中,文件系统的操作是使用fs核心模块来实现的。
针对这个fs,重点是如下两个方面:
- 常见的文件操作:读取、写入、删除、判断存在性、文件信息
- 常见的目录操作:创建、删除、读取
在fs模块中,基本上所有的操作都有两个版本:
- 同步版本
- 异步版本
读取文件
readFile / readFileSync
fs.readFile('./index.js','utf8',function(err,data){
if(err){
throw err
}
console.log(data);
})
fs.readFileSync('./index.js','utf8');
注意:
- 在读取文件的时候,默认使用的buffer,需要设置相应的字符集编码,对于文本文件,就设置为utf8.
- 在node的文件操作中,异步的回调函数是错误优先的回调,错误信息对象作为回调函数的第一个参数
同步和异步的区别
获取结果的方式不同,异步是通过回调的参数获取的,而同步是通过方法返回值获取的
执行顺序不同
覆盖式写文件
fs.writeFile(file,data,[options],callback) / fs.writeFileSync(file,data,[options])
eg:小文件拷贝
1、异步
fs.readFile('./a.js', function (err, data) {
if (err) {
throw err;
}
fs.writeFile('./b.js', data, function (err) {
if (err) {
throw err;
}
});
});
2、同步
fs.writeFileSync('./b.js', fs.readFileSync('./a.js'));
追加式写文件
fs.appendFile(file,data,[options],callback) / fs.appendFileSyncfs.writeFileSync(file,data,[options])
删除文件
fs.unlink(path,callback) / fs.unlinkSync(path)
判断文件是否存在
fs.exists(废弃) / fs.existsSync(path)
获取文件信息
fs.stat(path,callback) / fs.statSync(path)
callback 中文件信息对象 stats类
文件类型有很多种,在linux中,有7种。而在windows下面只有两种:
文件 stats.isFile()
目录 stats.isDirectory()
eg: 拷贝lib下的所以文件以及文件夹
fs.readdir('./lib', function (err, paths) {
if (err) {
throw err;
}
paths.forEach(function (file) {
fs.stat('./lib/' + file, function (err, st) {
if (err) {
throw err;
}
if (st.isFile()) {
fs.writeFileSync('./dist/' + file, fs.readFileSync('./lib/' + file));
} else {
if (!fs.existsSync('./dist/' + file)) {
fs.mkdirSync('./dist/' + file);
}
}
});
});
});
创建目录
fs.mkdir(path,callback) / fs.mkdirSync(path)
在使用mkdir创建目录的时候,只能创建单层目录,不能递归的创建目录。
path在书写路径格式时必须确认父路径存在才能创建子路径
删除目录
fs.rmdir(path,callback) / fs.rmdirSync(path)
rmdir只能删除空目录,如果目录是非空的,则无法删除
如果需要删除某个目录,首先就应该先删除目录中的内容,最后再删除目录本身
读取目录
fs.readdir(path,[options],callback) / fs.readdirSync(path,[options])
eg:拷贝文件夹下的所以文件
fs.readdir('./lib', function (err, paths) {
paths.forEach(function (file) {
fs.readFile('./lib/' + file, function (err, data) {
if (err) {
throw err;
}
fs.writeFileSync('./dist/' + file, data);
});
});
});
递归删除目录
由于rmdir方法,只能删除空目录,如果一个目录下有很多内容,尤其是嵌套了很多层的文件夹,只能一层一层的来删,应该使用递归的思想来实现。
只要有递归,就需要定义函数。
可以封装一个函数,用于删除指定目录。
如果没有递归,通常用异步的文件操作。有了递归,需要使用同步的文件操作。
node流
流是Node.js中一个非常重要的概念, 也是Node.js之所以适用于I / O密集型场景的重要原因之一。 流是Node.js移动数据的方式, 流可以是可读的和/ 或可写的。 在Node.js中很多模块都使用到了流, 包括HTTP和fs模块
由于数据是流, 这就意味着在完成文件读取之前, 从收到最初几个字节开始, 就可以对数据动作。
由于一次性读取再写入的方式不适用与大文件拷贝, 容易造成内存爆仓, 对于大文件, 我们只能读一点儿写一点儿直到拷贝完成
创建一个只读数据流
var rs = fs.createReadStream('./src/1.mp4');
创建一个只写数据流
var ws = fs.createWriteStream('./src/2.mp4');
管道流输出
rs.pipe(ws);
大文件拷贝实现原理
// 创建一个只读数据流
var readStream = fs.ReadStream('./src/1.mp4');
// 创建一个只写数据流
var writeStream = fs.WriteStream('./src/2.mp4');
// 在内存中监听到只读数据流时触发
rs.on('data', function (chunk) {
// ws.write(chunk); 将读取到的文件写入目标容器,返回布尔值 没写完-> false 写完-> true
if (!ws.write(chunk)) { // 如果没写完
rs.pause();// 暂停只读数据流
console.log('pause');
}
});
// drain事件来判断什么时候只写数据流已经将缓存中的数据写入目标,可以传入下一个待写数据
ws.on('drain', function () {
rs.resume(); // 循环读取
console.log('resume');
});
// 读完
rs.on('end', function () {
ws.end();
console.log('end');
});
eg:将src下的小文件拷贝到small目录中,大文件以流的形式拷贝到big目录中
fs.readdir('./src', function (err, paths) {
if (err) {
throw err;
}
paths.forEach(function (file) {
fs.stat('./src/' + file, function (err, st) {
if (err) {
throw err;
}
if (st.size / 1024 / 1024 > 300) {
createDir('./big', function () {
var rs = fs.createReadStream('./src/' + file);
var ws = fs.createWriteStream('./big/' + file);
rs.pipe(ws);
});
} else {
createDir('./small', function () {
fs.writeFileSync('./small/' + file, fs.readFileSync('./src/' + file));
});
}
});
});
});
function createDir (dist, ck) {
if (!fs.existsSync(dist)) {
fs.mkdirSync(dist);
}
ck && ck();
}