fs模块
1.在node中,与文件系统的交互是非常重要的,服务器的本质就是将本地的文件发送给远程的客户端。
2.Node
通过fs
模块来和文件系统进行交互。要使用fs
模块,要先对它进行加载。
fs
模块是一个核心模块,所以直接引入,不需要下载
var fs=require("fs");
- 该模块提供了本地文件的读写能力,基本上是
POSIX
文件操作命令的简单包装。 - 这模块几乎对所有操作提供异步和同步两种操作方式,供开发者选择。
同步文件系统会阻塞程序的执行,也就是除非操作完毕,否则不会向下执行代码。异步文件系统不会阻塞程序的执行,而是在操作完成时,通过回调函数将结果返回。
3.打开文件
- 同步打开一个文件
fs.openSync(path,flags[,mode])
flags-文件打开的行为
r 只读的
w 可写的
mode-设置文件模式(权限),文件创建默认权限为0666(可读可写),一般省略
var fs=require("fs");
var fd=fs.openSync("hello.txt","w");
- 异步打开一个文件
fs.open(path,flags[,mode],calback(err,fd))
参数的含义跟上面的同步方式一样。
calback-回调函数,带有两个参数,第一个表示错误信息 err,第二个是文件对象。
4.文件中写入内容
- 同步写入文件数据
fs.writeSync(fd,string[,position[,encoding]]);
-fd 文件的描述符,需要传递写入的文件的描述符
-string 要写入的内容
-position 表示写入文件开始位置
//操作文件之前先打开文件
var fs=fs.openSync("hello.txt","w");
//向文件中写入内容
fs.writeSync(fd,"hello world");
//使用完之后关闭文件
fs.closeSync(fd);
在我们演示的时候,不关闭文件似乎没啥影响,但是当我们在一台大型服务器(服务器一直在运行)上打开文件不关的时就会占用大量的内存,消耗大量的硬件资源。
- 异步法将数据写入文件
fs.write(fd,data[,option],calback);
//writeFile直接打开文件默认是w模式,所以如果文件存在,该方法写入的内容会覆盖旧的文件内容。
fd-文件名或文件描述符
data-写入文件的数据,可以使String(字符串)或Buffer(缓冲)对象。
options-该参数是一个对象,包含{encoding,mode,flag}。默认编码为utf8,模式为0666,flag为"w"。flag为“a”,表示追加数据,不会覆盖之前的数据。
callback-回调函数,回调函数只包含错误信息参数(err),在写入失败时返回。
var fs=fs.open("test.txt","w",fucntion(err,fd){
if(!err){
fs.write(fd,"hello world",function(err,f){
if(!err){
console.log("文件写入成功");
}else{
console.log(err)
}
//关闭文件
fs.close(fd,function(err){
if(!err){console.log("文件已经关闭")}
})
}
else{
console.log(err);
}
});
5.异步简单的文件写入(开发中常用的方式)
fs.writeFile(file, data[, options])
-file 要操作的文件的路径
-data 要写入的数据
-options 选项,可以对写入进行一些设置
encoding <string> | <null> 默认值: 'utf8'
mode <整数> 默认值: 0o666
flag <string>参见对文件系统的支持flags。默认值: 'w'。
signal <AbortSignal>允许中止正在进行的writeFile
-callback 当写入完成以后执行的函数
//引入fs模块 这个异步打开文件方式简单写法,需要手动打开文件
var fs=require("fs");
fs.writeFile("hello4.txt","这是通过write写入的内容",function(err){
if(!err){
console.log("写入成功")
}
})
//默认情况下,文件的操作形式是“w”,文件不存在则创建,文件存在,如果里面由内容,则直接覆盖重写。
//下面我们换成操作符"a",表示向文件中追加内容。
var fs=require("fs");
fs.writeFile("hello3.txt","我是hell3.txt的文本内容",{flag:'a'},functiont(err){
if(!err){
console.log("写入成功!")
}else{
console.log(err);
}
}){
}
当我们路径换成绝对路径时,完全可以替换路径的标识符\
为\\
或者为/
。
6.流式文件写入
正常的文件写入,相当于将一个文件中的所有内容一次性的输送到文件中。当写入的时候,那么就如上面的那种写法一样,首先得要把全部的文件内容准备好,然后程序执行,再将内容慢慢输送到文件中。但这样容易造成内存溢出,也就是说文件内容太大,所以程序在执行的时候,要开辟好大的一块空间来存储要输送的文件内容。
所以同步、异步、简单文件的写入方式都不适合大文件的写入,性能较差,容易导致内存溢出。流式文件的写入,可以分步的,持续的给文件中写入内容。
fs.createWriteStream(path[, options])
-可以用来创建一个可写流
-path 文件路径
-options 配置的参数
var ws=fs.crteateWirteStream("hello3.txt");
//通过ws向文件中输出内容 可以分多次写入文件
ws.write("通过可写流写入文件的内容");
ws.write("继续写入文件内容");
//可以通过监听流的open和close事件来监听流的打开和关闭
//on可以为对象绑定一个事件(事件字符串,回调函数) once(事件字符串,回调函数)
//而流文件的打开只需要一次,所以在写文件的时候,文件会自动打开,从而触发下面的文件打开监听事件
//这里使用once监听事件,而不使用on事件。
ws.once("open",function(){
console.log("流打开了");
})
上面说了流式文件的写入和文件打开时的事件监听,那么如何文件该如何关闭呢?
//文件在输送完数据之后,需要将文件关闭我们常规可能会想到使用ws.close()的方式关闭,但在这里并不合适正确的打开方式,请看下面:
//给文件流的关闭绑定一个监听事件
ws.once("close",function(){
console.log("流关闭了");
})
ws.write("我写入文件内容1");
ws.write("我写入文件内容2");
//关闭流
ws.end();
7.简单文件读取(同步文件读取,异步文件读取和上面的写文件类似,程序的可读性不好,我们就不在演示了)
异步写法:fs.readFile(path[, options], callback)
同步写法:fs.readFileSync(path[, options])
-path:要读取的文件路径
-options 读取的选项
-callbakc 回调函数,通过回调函数将读取到内容返回(err,data)
err 错误对象
data 读取到的数据,会返回一个Buffer。原因:因为读取的文件类型不确定,如果文件内容是一个图片或者音频则就无法将它以字符串类型输出。
var fs= require("fs");
fs.readFile("hello3.txt",function(err,data){
if(!err){
console.log(data); //输出的是buffer数据
console.log(data.toString())//如果数据是字符使用toString()转为字符串
}
}
//读取文件的还有一个目的就是将它写出到另一个文件,下面我们将使用读写的方式将文件内容写入另一个文件
var fs=require("fs");
fs.readFile("hello.txt",function(err,data){
if(!err){
fs.writeFile("world.txt",data,function(err){
if(!err){
console.log("文件写入成功!");
}
})
}
})
8.流式文件读取(适用于大文件)
如果文件太大,那么文件的保存又是一大问题。那么我们使用流式文件的方式读取,可以分多次将文件读取到内存中。
var fs=requrie("fs");
//创建一个可读流
var rs=fs.createReadStream("hello.jpg");
//创建一个可写流
var ws=fs.createWirteStream("world.jpg");
//监听流的开启和关闭
rs.once("open",function(){
console.log("可读流打开了");
});
//可读流自动关闭
rs.once("close",function(){
console.log("可读流关闭了");
//数据读取完毕,关闭可写流
ws.end();
});
ws.once("open",function(){
console.log("可写流 文件打开了");
});
ws.once("close",function(){
console.log("可写流,文件关闭了");
});
//如果要读取一个可读流中的数据,必须要为可读流绑定一个data事件,data事件绑定完毕,他会自动开始读取数据
rs.on("data",function(data){
console.log(data); //数据将会作为回调函数的参数返回。此时我们看到输出多条buffer数据
//把数据写入可写流
ws.write(data);
});
上面我们演示了如何打开一个可读流,并且将内容写入到可写流中,但是写法又是很繁杂的,下面介绍一种简单的写法
var fs=require("fs");
//创建一个可读流
var rs=fs.createReadStream("hello world1.mp3");
//创建一个可写流
var ws=fs.createWriteStream("hello world2.mp3");
//pipe()可将可读流中的内容,直接输到可写流中
rs.pipe(ws);
9.其他的操作
- 验证路径是否存在 可使用
fs.existsSync(path)
; - 获取文件信息;
- 同步获取文件状态
fs.stat("fs.html"\)
- 文件的信息
文件信息Stats的属性和方法:
size
: 文件大小; isFile()
:是否是一个文件 ; isDirectory()
:是否是一个文件夹 。。。。。
- 异步获取文件信息
fs.stat("fs.html",function(err,stat){console.log(stat)})
;stat中保留着文件的状态信息。
获取信息结果
10.删除文件
fs.unlink(path,callback)
fs.unlinkSync(path)
11.读取一个目录
fs.readdir(path[,options],callback)
fs.reddirSync(paht[,options])
-读取一个目录的目录结构 files是一个字符串数组,每一个元素就是一个文件夹或文件的名字
- 异步读取文件夹
可以看出输出的结果是一个包含在数组当中的文件名;
12.截断文件
将文件修改为指定的大小,第二个参数表示字节大小,超过字节的部分被删除
fs.truncate(path,len,callback)
fs.truncateSync(path,len)
13.创建文件夹
- 同步法创建文件夹
- 异步法创建文件夹
14.删除一个目录
fs.rmdir(path,callback)
fs.rmdirSync(paht)
\
15.对文件重命名
fs.rename(oldPath,newPath,callback)
fs.renameSync(oldPath,newPath)
16.监视文件的修改
fs.watchFile(filename[,options],listener)
-监视文件的修改
-参数:filename 要监视的文件的名字
-options:配置选项
-listener 回调函数就,当文件发生变化的时候回调函数会执行
//其中curr,prev文件都是一个stat对象,保存了文件的状态信息
fs.watchFile("hello world",function(curr,prev){
console.log("修改前文件"+prev);
console.log("修改后文件"+curr);
})
监听的工作原理:该方法会每隔一段时间检查文件的状态,一旦发生变化,则执行回调函数。工作原理我们知道了,但是
这里所说的一段时间又是多久呢?时间长了,无法及时监听到文件的变化,时间短了,会消耗系统资源。下面我们介绍option中的一个配置选项。
fs.watchFile("hello world",{inteval:1000},function(curr,prev){
console.log("每隔一秒进行一次监听");
})
学过上面的内容,我们做一个实例
const fs=require("fs");
var path='./upload';
//查找一个目录,如果不存在创建该目录
fs.stat(path,(err,data)=>{
if(err){
//执行创建目录
mkdir(path);
return ;
}
//如果是一目录
if(data.isDirectory()){
console.log("upload目录存在");
}else{
//先删除再创建
fs.unlink(path,(err)=>{
if(!err){
//执行创建目录
mkdir(path);
}else{
console.log("创建失败");
}
})
}
})
//文件创建函数
function mkdir(dir){
fs.mkdir(dir,(err)=>{
if(err){
console.log(err);
return ;
}
})
}
当然手动的创建一个目录似乎有点麻烦,接下来,我们使用第三方模块来实现文件夹的创建,实现和上面同样的效果。
1.安装 npm i mkdirp --save
2.使用
var mkdirp=require('mkdirp');
//创建一个文件夹
mkdirp("./upload",function(err){
if(err){
console.log(err);
}
})
mkdirp
的创建层级目录
mkdirp("./upload/aaa/xxx",function(err){
if(err){
console.log(err);
}
})
上面我们知道可以使用fs.readdir()
这个方法来读取一个目录,并且读取的所有内容都保存在一个数组中,但现在我们只想要将一个目录中的目录放入一个数组,又怎么实现呢?(使用异步方法操作文件)
var fs=require('fs');
var dirArr=[];
var path='./upload';
fs.readdir(paht,(err,data)=>{
if(err){
console.log(err);
return ;
}
//使用递归法实现
(function fn1(i){
if(i<data.length){
fs.stat(path+'/'+data[i],(err,data)=>{
if(status.isDirectory(){
dirArr.push(data[i]);
})
fn1(++i);
})
else{
console.log(dirArr)
return ;
}
}
})(0)
})
//上面我们使用了递归法来获取了一个目录中的目录数组,但是代码可读性不高,下面我们使用async,await来处理异步
var fs=require('fs');
var path='./wwwroot';
var dirArr=[];
function getDir(data){
//下面的回调函数定义为async
fs.readdir(path,async (err,data)=>{
if(err){ console.log(err); return ;}
//关键一步,使用循环的方式来实现多个异步过程
for(var i=0;i<data.length;i++){
if(await fn1(path+'/'+data[i])){
dirArr.push(data[i]);
}
}
//输出最后得到的目录数组
console.log(dirArr);
}
});
function fn1(data){
return new Promise(funciton(resolve,reject){
fs.stat(data,(err,stats)=>{
if(err){
console.log(err);
//查看文件失败,返回给调用的await一个错误信息。
reject(err);
//提前结束本次异步过程
return ;
}
if(stats.isDirecotry()){
//其实这个地方已经就能判断出是否是目录了,之所以返回的是一个布尔值
//最重要的一个原因是降低代码之间的耦合度。这里你细细品!!!
resolve(true);
}else{resolve(false)}
})
})
}
if(err){
console.log(err);
//查看文件失败,返回给调用的await一个错误信息。
reject(err);
//提前结束本次异步过程
return ;
}
if(stats.isDirecotry()){
//其实这个地方已经就能判断出是否是目录了,之所以返回的是一个布尔值
//最重要的一个原因是降低代码之间的耦合度。这里你细细品!!!
resolve(true);
}else{resolve(false)}
})
})
}