Node.js文件IO(Node.js自学第十天)

1,577 阅读8分钟

fs模块是Node.js模块核心模块之一,提供了符合POSIX规范的操作文件和目录的基本方法。这些方法都有异步和同步两种形式。在I/O操作密集型的应用中,推荐使用异步调用方式,以避免整个应用程序的阻塞。

所有异步方法的回调函数中第一个参数都是一个IO错误对象:

/**同步调用方法***/
const fs=require('fs');

fs.unlinkSync('/f2');
console.log('succ');
/**异步调用方法**/
const fs=require('fs');

fs.unlink('/f1',(err)=>{
	if(err)throw err;
  console.log('succ');
})

文件模式

类Unix文件系统中,针对文件和目录的操作,为了控制不同用户不同权限,定义了如下的操作权限:

r:Read,读取权限,对应数字为4
w:Write,写出权限,对应数字为2
x:eXecute,执行权限,对应数字为1

不同的用户又分为如下三种类型:
u:User,文件所有者
g:Group,文件所有者所在组的其他成员
o:Other,其他用户

-rwx------	700			文件所有者对文件具有读取、写入和执行的权限。
-rwxr--r--	744			文件所有者具有读、写与执行权限,其他用户则具有读取的权限。
-rw-rw-r--	664			文件所有者与同组用户对文件具有读写的权限,而其他用户具有读取的权限
drwx--x--x	711			目录所有者具有读写与进入目录的权限,其他用户只能进入该目录。

常用class

class名 说明
fs.Stats 文件或目录的统计信息描述对象
fs.ReadStream stream.readable接口的实现对象
fs.WriteStream stream.Writeable接口实现对象
fs.FSWatcher 可用于监视文件修改的文件监视器对象

常用方法

方法名 说明
fs.mkdir() 创建目录
fs.rmdir() 删除目录
fs.readFile() 读取文件内容
fs.writeFile() 向文件写出内容
fs.apendFile() 向文件追加内容
fs.unlink() 删除文件
fs.rename() 重命名文件

文件信息统计

fs.stat( )和fs.statSync( )方法用于返回一个文件或目录的统计信息对象(fs.Stats类型)。

const fs=require('fs');
fs.stat("./app.log",(err,stats)=>{
    if(err) throw err;
    console.log(stats);
  	console.log(stats.isFile())
})

/****输出获取到的信息*******/
{
  dev: 1786362065,
  mode: 33206,
  nlink: 1,
  uid: 0,
  gid: 0,
  rdev: 0,
  blksize: 4096,
  ino: 1407374883658375,
  size: 12,
  blocks: 0,
  atimeMs: 1572344727794.7805,
  mtimeMs: 1572344727414.4744,
  ctimeMs: 1572344727414.4744,
  birthtimeMs: 1572344727413.4426,
  atime: 2019-10-29T10:25:27.795Z,
  mtime: 2019-10-29T10:25:27.414Z,
  ctime: 2019-10-29T10:25:27.414Z,
  birthtime: 2019-10-29T10:25:27.413Z
}

true

fs.Stats对象还提供了如下方法可用于检查文件的物理特性:

方法名 说明
stats.isFile() 是否为文件
stats.isDireCtory() 是否为目录
stats.isBlockDevice() 是否为块设备,如磁盘
stats.isCharacterDevice() 是否为字符设备,如键盘
stats.isFIFO() 是否为FIFO设备,如打印机
stats.isSocket() 是否为Socket文件
const path='./htdocs';
fs.stat(path,(err,stats)=>{
    if(err){
        fs.mkdir(path);
    }else{
        fs.readdir(path,(err,list)=>{
            console.log(list)
        })
    }
})

操作目录

方法名 说明
fs.mkdir(path[,mode],callback) 创建指定目录(mode就是文件权限)
fs.mkdirSync(path[,mode]) 创建指定目录
fs.rmdir(path,callback) 删除指定目录
fs.rmdirSync(path) 删除指定目录
fs.readdir(path[,options],callback) 读取目录下内容
fs.readdirSync(path[,options]) 读取目录下内容

创建一个htdocs目录,当前用户、当前用户所在分组其他成员、其他成员具有读写和执行的权限:

const fs=require('fs');
fs.mkdir('./htdocs',777,(err)=>{
    if(err)throw err;
    console.log("目录创建成功!");
})

读取htdocs目录下的内容:

fs.readdir("./htdocs",(err,list)=>{
    if(err)throw err;
    console.log(list);
});
//[ 'img','index.html' ]

删除htdocs目录下的img文件夹:

fs.rmdir('./htdocs/img',(err)=>{
    if(err) throw err;
    console.log("目录删除成功!")
})

读写文件全部内容

对于数据量不是很大的文件,可以一次性的读写其中的全部内容。

方法名 说明
fs.readFile(file[,options],callback) 读取文件内容
fs.writeFile(file,data[,options],callback) 写出内容
fs.apendFile(file,data[,options],callback) 追加内容

读取文件内容:

const path='./htdocs/index.html';
fs.readFile(path,(err,data)=>{
    if(err)throw err;
    console.log(data);
    console.log(data.toString())
})

向文件写出内容:

const path='./htdocs/index.html';
var data="<h1>你好世界!</h1>"
fs.writeFile(path,data,(err)=>{
    if(err)throw err;
    console.log("写入数据完成!");
})

注意:若文件不存在,则writeFile方法会自动创建该文件;若目标文件存在,该方法会覆盖原有所有内容。

向文件追加内容:

const path='./htdocs/index.html';
var data="<h2>我很好!</h2>"
fs.appendFile(path,data,(err)=>{
    if(err)throw err;
    console.log("追加数据完成!");
})

注意:若目标文件不存在,则appendFile方法会自动创建该文件;若目标文件存在,该方法会在原来内容后面追加新内容。

读写文件部分内容

腹泻文件的部分内容,需要安装如下步骤:
(1)打开指定文件,获取对应的"文件描述符(File Descriptor)"
(2)读取或写入文件指定部分的内容;
(3)关闭文件待其他进程使用。

方法名 说明
fs.open(path[,mode],callback) 打开文件
fs.read(fd,buffer,offset,length,potion,callback) 读取文件(offset是buffer的存储偏移量,length是buffer存储数据最大长度,option从文件内开始读取的位置)
fs.write(fd,offset,length[,position],callback) 写出文件
fs.close(fd,callback) 关闭文件

读取市部分内容:

const src='./htdocs/index.html';
fs.open(src,(err,fd)=>{
    if(err)throw err;
    const buf=Buffer.alloc(1024);
    fs.read(fd,buf,0,1024,0,(err,bytesRead,buffer)=>{
        if(err)throw err;
      	console.log("实际读取道德字节数:%d",bytesRead);
        console.log(buffer.toString('UTF-8',0,bytesRead));
        fs.close(fd,(err)=>{
            if(err)throw err;
            console.log('文件已关闭!')
        });
    })
});
//45
//<h1>你好世界!</h1><h2>我很好!</h2>
//文件已关闭!

写入部分内容:

const src='./htdocs/index.html';
fs.open(src,'w',(err,fd)=>{
    if(err)throw err;
    const buf=Buffer.from("<h3>真好!</h3>");
    fs.write(fd,buf,0,'UTF-8',(err,bytesRead,string)=>{
        if(err)throw err;
        console.log("写入部分内容成功!");
        console.log("写入字节数为:%d,写入内容为:%s",bytesRead,string)
        fs.close(fd,(err)=>{
            if(err)throw err;
            console.log('文件已关闭!')
        });
    })
});
//写入部分内容成功!
//写入字节数为:18,写入内容为:<h3>真好!</h3>
//文件已关闭!

文件的流式操作

流(Stream),是一组有序的,有起点和终点的字节或字符集数据的集合。

Node.js中,stream模块中的Readable接口的实现对象都可以来读取文件中的部分数据暂存在缓冲区中,如:fs.ReadStream、http.IncomingMessage、net.Socket、process.stdin等;这些对象都继承了EventMitter类,在读取数据过程中,可以触发各种流读取相关事件。

stream模块中的Writable接口的实现对象都可以用来写出数据到缓冲区中,待缓冲区满后向物理介质输出:fs.WriteStream、http.ClienrRequest、http.ServerResponse、net.Socket、process.stdout等;这些对象都继承了EventMitter类,在写出数据过程中,可以触发各种数据写出取相关事件。

输入流对象

stream.Readable对象可以触发下列事件:

事件名 说明
readable 当可以从流中读取数据时触发
data 当读取到数据时触发
end 当读取到流结尾时触发
error 当出现读取错误时触发
close 当流关闭时触发

stream.Readable对象可以执行如下操作:

方法名 说明
read() 读取数据
setEncoding() 设置字符编码
pause() 暂停读取
resume() 继续读取
pipe() 设置一个数据通道
unpipe() 取消设置的数据通道
const fs=require('fs');

var read=fs.createReadStream('./htdocs/1.mp4');

var total=0;
read.on("data",(buf)=>{
    console.log("数据长度:%d",buf.length);
    total+=buf.length;
})
read.on("end",(err)=>{
    if(err)throw err;
    console.log("文件读取结束!");
    console.log("读取到总字节数:%d",total);
})
read.on("close",(err)=>{
    if(err)throw err;
    console.log("流成功关闭!")
})
//数据长度:65536
//数据长度:65536
//...
//数据长度:65536
//数据长度:65536
//数据长度:65536
//数据长度:50848
//文件读取结束!
//读取到总字节数:117819040
//流成功关闭!

输出流对象

stream.Writeable对象可以触发下列事件:

事件名 说明
drain 当输出缓冲区已经排空时触发
finish 当end()方法吧所有数据写出到缓冲区时触发
pipe 当用于读取数据的对象调用pipe()时触发
unpipe 当用于读取数据的对象调用unpipe()时触发
error 当输出过程产生错误时触发

stream.Writeable对象可以执行如下操作:

方法名 说明
write() 写出数据
end() 当没有更多数据需要再被输出时调用,此方法将清空输出缓冲区

使用readStream和writeStream实现一个文件拷贝功能:

const fs=require('fs');

var read=fs.createReadStream('./htdocs/1.mp4');
var write=fs.createWriteStream("./htdocs/1_copy.mp4");
var total=0;
read.on("data",(buf)=>{
    console.log("数据长度:%d",buf.length);
    total+=buf.length;
    write.write(buf,0,buf.length);
})
read.on("end",(err)=>{
    if(err)throw err;
    console.log("文件读取结束!");
    console.log("读取到总字节数:%d",total);
    write.end();
});
read.on("close",(err)=>{
    if(err)throw err;
    console.log("流成功关闭!")
})
write.on("finish",(err)=>{
    if(err)throw err;
    console.log("文件写出完成!")
})
//数据长度:65536
//...
//数据长度:50848
//文件读取结束!
//读取到总字节数:117819040
//文件写出完成!
//流成功关闭!

使用管道

Pipe:管道,可以用于自动的去读一个Readable对象中的所有数据,并写出到指定的Writeable对象中。

const fs=require("fs");

const src="./htdocs/index.html";
const dest="./backup/index.html";

var read=fs.createReadStream(src);
var write=fs.createWriteStream(dest);

read.pipe(write);		//使用管道实现自动读写
reader.on("end",(err)=>{
	if(err)throw err;
  console.log("文件已经复制完成!")
})