当前位置: 首页 > 后端技术 > Node.js

Node.js进阶进阶fs文件模块学习

时间:2023-04-03 11:25:12 Node.js

人缺的不是天赋而是志向,不是成功的能力而是努力的意志。——卜尔维文章同步到github博客:https://github.com/koala-codi...前言文件操作是开发过程中不可或缺的一部分。Node.js中的fs模块是对文件操作的封装,提供了文件读取、写入、重命名、删除、遍历目录、链接等POSIX文件系统操作。与其他模块不同的是,fs模块中的所有操作都提供了异步和同步两种版本。有sync后缀的方法是同步方法,没有sync后缀的方法是异步方法。计算机系统和文件概述文章一些常识--权限位模式--标志位--文件描述符fsNode.js中fs模块的API详解及对应Demo--常规文件操作--高级文件操作--Node.js中文件目录操作fs模块的api对应demofs模块的应用场景和实战训练(复制大小文件)。面试会问fs模块有几个常用函数?什么情况下使用fs.open读取文件?用fs模块写个复制大文件的例子(注意大文件)?文件常识计算机中的一些文件知识,文件权限位模式,标识位标志,文件描述符fd等,你需要了解。这些内容对你学习fs的api、记忆和使用都有很大的帮助。权限位模式因为fs模块需要操作文件,所以会涉及到操作权限的问题,所以需要知道文件权限是什么,有什么权限。文件权限表:在上表中,我们可以看到系统分配了三类权限,分别是文件所有者(自己)、文件所属组(家人)和其他用户(陌生人),以及文件操作权限分为三种,read,write,execute。数字以八进制数表示。有权限的八进制数是4、2、1,没有权限的是0。为了便于理解,我们可以在任意目录下打开Git,使用Linux命令ls-al查看drwxr-xr-x目录下文件和文件夹的权限1koala1971210Jun2814:41core-rw-r--r--1koala197121293Jun2317:44index.md在上面的目录信息中,很容易看到用户名、创建时间和文件名等信息,但最重要的是第一项在开始(十个字符)。第一位代表是文件还是文件夹,d开头代表文件夹,-开头代表文件,后面九位代表当前用户的权限位,用户所属组属于,和其他用户。除以三个数字,分别代表读(r)、写(w)和执行(x),-表示当前位没有对应的权限。权限参数方式主要针对Linux和Unix操作系统。Window默认的权限是可读、可写、不可执行,所以权限位数表示为0o666,转换后的十进制表示为438。在flagNode.js中,flag表示文件的操作方式,如可读、可写、可读可写等。下面用一张表来表示标志位及其对应的文件操作含义。符号含义r读取文件,如果文件不存在则抛出异常。r+读写一个文件,如果文件不存在则抛出异常。rs读写文件,指示操作系统绕过本地文件系统缓存。w写入文件,如果文件不存在则创建,如果存在则清空写入。wx写入文件并以独占方式打开它。w+读写文件,文件不存在则创建,存在则清空后再写入。wx+和w+类似,都是独占打开的。a追加写入,如果文件不存在,则创建文件。ax类似于a,独占打开。a+读取并追加写入,如果不存在则创建。ax+和a+类似,都是独占打开的。上表是这些flags的具体字符和含义,但是flags不经常用,也不好记,所以下面总结了一个加快记忆的方法。r:读w:写s:同步+:增加相反的操作x:独占方式r+和w+的区别,当文件不存在时,r+不会创建文件,但会抛出异常,而w+会创建文件;如果文件存在,r+不会自动清除文件,但w+会自动清除已有文件的内容。文件描述符fs操作系统为每个打开的文件分配一个称为文件描述符的数字标识符。文件操作使用这些文件描述符来识别和跟踪每个特定文件。Window系统使用不同但相似的概念。为了方便用户,NodeJS抽象了不同操作系统之间的差异,并为所有打开的文件分配了数字文件描述符。在Node.js中,每操作一次文件,文件描述符就会递增,文件描述符一般从3开始,因为前面有3个特殊的描述符,0、1、2,分别代表process.stdin(标准输入)、process.stdout(标准输出)和process.stderr(错误输出)。文件操作完整性读写文件操作fileread-fs.readFilefs.readFile(filename,[encoding],[callback(error,data)]filereadfunction接收第一个必传参数filename,表示读取的文件名。第二个参数encoding可选,表示文件的字符编码,第三个参数callback是一个回调函数,用于接收文件的内容,说明:如果不指定encoding,则callback为第二个参数,callback函数提供两个参数err与data,err表示是否有错误,data为文件内容,如果指定了encoding,data为解析后的字符串,否则为Buffer形式表示的二进制数据。演示:constfs=require('fs');constpath=require('path');constfilePath=path.join(__dirname,'koalaFile.txt')constfilePath1=path.join(__dirname,'koalaFile1.txt'')//--异步读文件fs.readFile(filePath,'utf8',function(err,data){console.log(data);//程序员成长指南});//--同步读Fetch文件constfileResult=fs.readFileSync(filePath,'utf8');console.log(fileResult);//程序员长大写文件到fs.writeFilefs.writeFile(filename,data,[options],callback)file第一个必选参数写操作的参数是filename,表示要读取的文件名。第二个参数是要写入的数据。第三个参数option是一个对象,如下编码{String|null}default='utf-8'mode{Number}default=438(aka0666inOctal)flag{String}default='w'这时候第一章提到的计算机知识就会用到。标志值,默认为w,会清空文件再写入。标志值,r代表读文件,w代表写文件,a代表追加。demo://写入文件内容(如果文件不存在,则创建一个文件)//写入时,先清空文件fs.writeFile(filePath,'写入成功:程序员成长指南',function(err){if(err){throwerr;}//写入成功后读取测试vardata=fs.readFileSync(filePath,'utf-8');console.log('newdata-->'+data);});//文件追加也可以通过写文件和使用flags来实现fs.writeFile(filePath,'程序员成长指南添加的数据',{'flag':'a'},function(err){if(err){throwerr;}console.log('success');vardata=fs.readFileSync(filePath,'utf-8')//写入成功后读取测试console.log('Addeddata-->'+数据);});FileAppend-appendFilefs.appendFile(filename,data,[options],callback)第一个必选参数filename表示要读取的文件名。第二个参数data,data可以是任意字符串或者cache第三个参数option是一个对象,与write不同的是[options]的flag默认值为“a”,所以它以追加的方式写入数据。说明:该方法异步写入数据Insert到文件中,如果文件不存在,会自动创建demo//--异步另一个文件追加操作(非覆盖模式)//写入文件内容(如果文件不存在,会创建一个文件)fs.appendFile(filePath,'新数据程序员成长指南北456',function(err){if(err){throwerr;}//写入成功后读取测试var数据=fs.readFileSync(文件路径,'utf-8');console.log(data);});//--同步另一个文件追加操作(非覆盖模式)fs.appendFileSync(filePath,'同步追加新数据)');copyfile-copyFilefs.copyFile(filenameA,filenameB,callback)第一个参数为原文件名,第二个参数为要复制到demo中的文件名://复制filePath文件内容到filePath1文件内容fs.copyFileSync(filePath,filePath1);letdata=fs.readFileSync(filePath1,'utf8');console.log(数据);//程序员删除文件的成长指南-unlinkfs.unlink(filename,callback)第一个参数文件路径大家应该都知道,我就不赘述了第二个回调函数callbackdemo://--异步文件删除fs.unlink(filePath,function(err){if(err)return;});//--同步删除文件fs.unlinkSync(filePath,function(err){if(err)return;});指定位置读写文件操作(高级文件操作)接下来的高级文件操作会和上面的有些不同,过程稍微复杂一些,需要先用fs.open打开文件,然后就可以了使用fs.read读取,或使用fs.write写入文件,最后,您需要使用fs.close关闭文件。特别说明:read方法与readFile不同,一般针对文件过大无法一次将所有内容读入缓存或者文件大小未知的情况,多次读入Buffer。如果想了解Buffer,可以阅读NodeJS——Buffer解读。(注意这个被我的文章换掉了)打开文件-fs.openfs.open(path,flags,[mode],callback)第一个参数:文件路径第二个参数:同上面提到的标识符flagbeginning第三个第一个参数:[mode]为文件的权限(可选参数,默认值为0666)第四个参数:callback回调函数demo:fs.open(filePath,'r','0666',function(err,fd){console.log('hahaha',fd);//返回的第二个参数是一个整数,表示打开文件返回的文件描述符,也就是窗口中的文件句柄})demo说明:第二个返回的参数是一个整数,表示打开文件返回的文件描述符。在window中也称为文件句柄,文件描述符在开头也有说明。文件读取-fs.readfs.read(fd,buffer,offset,length,position,callback);六个参数fd:文件描述符,需要先用open打开,fs.open打开成功后返回的文件描述符;buffer:Buffer对象,v8引擎分配的一段内存,要读取的Buffer内容进入;offset:整数,写入Buffer缓冲区的初始位置,以字节为单位;length:整数,读取文件长度;position:整数,读取文件的初始位置;文件大小以字节为单位Bufferobject),读取执行完成后执行。demo:constfs=require('fs');letbuf=Buffer.alloc(6);//创建一个长度为6字节的buf缓存对象//打开文件fs.open('6.txt','r',(err,fd)=>{//读取文件fs.read(fd,buf,0,3,0,(err,bytesRead,buffer)=>{console.log(bytesRead);console.log(buffer);//继续读取fs.read(fd,buf,3,3,3,(err,bytesRead,buffer)=>{console.log(bytesRead);console.log(buffer);console.log(buffer.toString());});});});//3////3////Hello文件写入-fs.writefs.write(fd,buffer,offset,length,position,callback);六个参数fd:文件描述符,用fs.open打开成功后返回;buffer:Buffer对象,v8引擎内存分配的一段,存放要写入文件数据的Buffer;offset:整数,从Buffer缓冲区读取数据的初始位置,单位为字节;length:整数,读取Buffer数据的字节数;position:一个整数,写回车文件的起始位置;callback:写操作完成后的回调函数,有三个参数err(错误),bytesWritten(实际写入的字节数),buffer(要读取的buffer对象),写完成后实现。demo:fileclose-fs.closefs.close(fd,callback)第一个参数:打开fd文件时传入的文件描述符第二个参数callback回调函数,回调函数有一个参数err(error),关闭文件后实施。demo://注意文件描述符fdfs.open(filePath,'r',(err,fd)=>{fs.close(fd,err=>{console.log('关闭成功');//关闭成功});});目录(文件夹)操作1、fs.mkdir创建目录fs.mkdir(path,[options],callback)第一个参数:path目录路径第二个参数[options],递归Default:错误的。modeWindows不支持。默认值:0o777。可选选项参数可以是指定模式(权限和粘性位)的整数,或具有模式属性和递归属性(指示是否应创建父文件夹)的对象。第三个参数是回调函数。回调函数有一个参数err(error),关闭文件后执行。demo:fs.mkdir('./mkdir',function(err){if(err)return;console.log('Directorycreatedsuccessfully');})注意:在Windows上,在根目录上使用fs.mkdir()(即使使用递归参数)也会导致错误:fs.mkdir('/',{recursive:true},(err)=>{//=>[Error:EPERM:operationnotpermitted,mkdir'C:\']});2.fs.rmdir删除目录fs.rmdir(path,callback)第一个参数:path目录路径第三个参数回调函数,回调函数有一个参数err(错误),关闭文件后执行。demo:constfs=require('fs');fs.rmdir('./mkdir',function(err){if(err)return;console.log('删除目录成功');})注意:在文件在目录(而不是目录)上使用fs.rmdir()会在Windows上导致ENOENT错误,在POSIX上导致ENOTDIR错误。3.fs.readdir读取目录fs.readdir(path,[options],callback)第一个参数:path目录路径第二个参数[options]可选的options参数可以是字符串指定代码,也可以是对象具有一个编码属性,该属性指定用于传递给回调的文件名的字符编码。如果encoding设置为'buffer',则返回的文件名是一个Buffer对象。如果options.withFileTypes设置为true,文件数组将包含fs.Dirent对象。第三个参数是回调函数。回调函数有两个参数,第一个是err(错误),第二个返回的数据是一个数组,包括文件夹中的所有文件,是目录中文件名的数组(不包括'.'和'..')。demo:constfs=require('fs');fs.readdir('./file',function(err,data){if(err)return;//data是一个数组console.log('readdataFor:'+数据[0]);});实战训练:只讲文件相关的API,好像很无聊。下面说说fs在Node.js中的一些具体应用《实例:fs模块如何实现文件复制》文件复制示例包括小文件复制和大文件复制(前面提到的fs模块也可以实现文件复制)小文件复制小文件复制除了上面fs本身提供的api,我们还可以通过读写完成一个复制例子,如下://fileCopy将data.txt文件的内容复制到copyData.txt//读取fileconstfileName1=path.resolve(__dirname,'data.txt')fs.readFile(fileName1,function(err,data){if(err){//错误console.log(err.message)返回}//获取文件内容vardataStr=data.toString()//写入文件constfileName2=path.resolve(__dirname,'copyData.txt')fs.writeFile(fileName2,dataStr,function(err){if(err){//Errorconsole.log(err.message)return}console.log('Copysuccessful')})})我们使用readFile和writeFile实现了一个拷贝函数,拷贝函数是将拷贝文件的数据读入到一次内存,一次写入目标文件,对小文件有好处。如果大文件副本是一次几百M的大文件,一次读写不现实,需要多次读写。接下来用文件操作的高级方法实现一个大文件,文件大小未知。复制功能。当然除了这个方法之外,我在上一篇文章中提到的stream模块也可以实现,而且性能更好,这里不再赘述。本文主要讲fs模块。demo://copymethodfunctioncopy(src,dest,size=16*1024,callback){//打开源文件fs.open(src,'r',(err,readFd)=>{//打开目标文件fs.open(dest,'w',(err,writeFd)=>{letbuf=Buffer.alloc(size);letread=0;//下次读取文件的位置letwritten=0;//next第一次写入的文件位置(functionnext(){//readfs.read(readFd,buf,0,size,read,(err,bytesRead)=>{read+=bytesRead;//if没有内容关闭文件if(!bytesRead)fs.close(readFd,err=>console.log('closethesourcefile'));//writefs.write(writeFd,buf,0,bytesRead,written,(err,bytesWritten)=>{//如果同步缓存中没有内容,关闭文件后执行回调if(!bytesWritten){fs.fsync(writeFd,err=>{fs.close(writeFd,err=>return!err&&callback());});}written+=bytesWritten;//继续读写next();});});})();});});}in在上面的copy方法中,我们手动维护了下一个读位置和下一个写位置。如果read和written的参数为null,NodeJS会自动帮我们维护这两个值。现在有一个文件6.txt,内容是“Hello”,一个空文件7.txt,我们把6.txt的内容写入7.txt。constfs=require('fs');//缓冲区的长度constBUFFER_SIZE=3;//复制文件内容写入copy('6.txt','7.txt',BUFFER_SIZE,()=>{fs.readFile('7.txt','utf8',(err,data)=>{//复制并读取7.txt的内容后console.log(data);//你好});});在NodeJS中对于文件操作,多次读写时,读取数据的大小一般为64k,写入数据的大小为16k。大家好,我是考拉,正在做Node.js进阶路线。今天就分享这么多。如果对分享的内容感兴趣,可以关注公众号《程序员成长指南》,或者加入技术交流群,一起探讨。