node.js文件操作系统实例详解
本文实例讲述了node.js文件操作。分享给大家供大家参考,具体如下:
普通读取
同步读取
varfs=require('fs'); vardata; try{ data=fs.readFileSync('./fileForRead.txt','utf8'); console.log('文件内容:'+data); }catch(err){ console.error('读取文件出错:'+err.message); }
输出如下:
/usr/local/bin/nodereadFileSync.js
文件内容:helloworld
异步读取
varfs=require('fs'); fs.readFile('./fileForRead.txt','utf8',function(err,data){ if(err){ returnconsole.error('读取文件出错:'+err.message); } console.log('文件内容:'+data); });
输出如下
/usr/local/bin/nodereadFile.js
文件内容:helloworld
通过文件流读取
适合读取大文件
varfs=require('fs'); varreadStream=fs.createReadStream('./fileForRead.txt','utf8'); readStream .on('data',function(chunk){ console.log('读取数据:'+chunk); }) .on('error',function(err){ console.log('出错:'+err.message); }) .on('end',function(){//没有数据了 console.log('没有数据了'); }) .on('close',function(){//已经关闭,不会再有事件抛出 console.log('已经关闭'); });
输出如下
/usr/local/bin/nodecreateReadStream.js
读取数据:helloworld
没有数据了
已经关闭
备注:以下代码,如果文件不存在,则创建文件;如果文件存在,则覆盖文件内容;
异步写入
varfs=require('fs'); fs.writeFile('./fileForWrite.txt','helloworld','utf8',function(err){ if(err)throwerr; console.log('文件写入成功'); });
同步写入
varfs=require('fs'); try{ fs.writeFileSync('./fileForWrite1.txt','helloworld','utf8'); console.log('文件写入成功'); }catch(err){ throwerr; }
通过文件流写入
varfs=require('fs'); varwriteStream=fs.createWriteStream('./fileForWrite1.txt','utf8'); writeStream .on('close',function(){//已经关闭,不会再有事件抛出 console.log('已经关闭'); }); writeStream.write('hello'); writeStream.write('world'); writeStream.end('');
相对底层的接口
fs.write(fd,buffer,offset,length[,position],callback)fs.write(fd,data[,position[,encoding]],callback)fs.writeSync(fd,buffer,offset,length[,position])fs.writeSync(fd,data[,position[,encoding]])
- fd:写入的文件句柄。
- buffer:写入的内容。
- offset:将buffer从offset位置开始,长度为length的内容写入。
- length:写入的buffer内容的长度。
- position:从打开文件的position处写入。
- callback:参数为(err,written,buffer)。written表示有xx字节的buffer被写入。
备注:fs.write(fd,buffer,offset,length[,position],callback)跟fs.write(fd,data[,position[,encoding]],callback)的区别在于:后面的只能把所有的data写入,而前面的可以写入指定的data子串?
fs.exists()已经是deprecated状态,现在可以通过下面代码判断文件是否存在。
varfs=require('fs'); fs.access('./fileForRead.txt',function(err){ if(err)throwerr; console.log('fileForRead.txt存在'); }); fs.access('./fileForRead2.txt',function(err){ if(err)throwerr; console.log('fileForRead2.txt存在'); });
fs.access()除了判断文件是否存在(默认模式),还可以用来判断文件的权限。
备忘:fs.constants.F_OK等常量无法获取(nodev6.1,mac10.11.4下,fs.constants是undefined)
创建目录
异步版本(如果目录已存在,会报错)
varfs=require('fs'); fs.mkdir('./hello',function(err){ if(err)throwerr; console.log('目录创建成功'); });
同步版本
varfs=require('fs'); fs.mkdirSync('./hello');
删除文件
varfs=require('fs'); fs.unlink('./fileForUnlink.txt',function(err){ if(err)throwerr; console.log('文件删除成功'); }); varfs=require('fs'); fs.unlinkSync('./fileForUnlink.txt');
//fs.mkdir(path[,mode],callback) varfs=require('fs'); fs.mkdir('sub',function(err){ if(err)throwerr; console.log('创建目录成功'); }); //fs.mkdirSync(path[,mode]) varfs=require('fs'); try{ fs.mkdirSync('hello'); console.log('创建目录成功'); }catch(e){ throwe; }
同步版本,注意:fs.readdirSync()只会读一层,所以需要判断文件类型是否目录,如果是,则进行递归遍历。
//fs.readdirSync(path[,options]) varfs=require('fs'); varpath=require('path'); vargetFilesInDir=function(dir){ varresults=[path.resolve(dir)]; varfiles=fs.readdirSync(dir,'utf8'); files.forEach(function(file){ file=path.resolve(dir,file); varstats=fs.statSync(file); if(stats.isFile()){ results.push(file); }elseif(stats.isDirectory()){ results=results.concat(getFilesInDir(file)); } }); returnresults; }; varfiles=getFilesInDir('../'); console.log(files);
异步版本:(TODO)
//fs.rename(oldPath,newPath,callback) varfs=require('fs'); fs.rename('./hello','./world',function(err){ if(err)throwerr; console.log('重命名成功'); }); fs.renameSync(oldPath,newPath) varfs=require('fs'); fs.renameSync('./world','./hello');
fs.watch()比fs.watchFile()高效很多(why)
fs.watchFile()
实现原理:轮询。每隔一段时间检查文件是否发生变化。所以在不同平台上表现基本是一致的。
varfs=require('fs'); varoptions={ persistent:true,//默认就是true interval:2000//多久检查一次 }; //curr,prev是被监听文件的状态,fs.Stat实例 //可以通过fs.unwatch()移除监听 fs.watchFile('./fileForWatch.txt',options,function(curr,prev){ console.log('修改时间为:'+curr.mtime); });
修改fileForWatch.txt,可以看到控制台下打印出日志
/usr/local/bin/nodewatchFile.js
修改时间为:SatJul16201619:03:57GMT+0800(CST)
修改时间为:SatJul16201619:04:05GMT+0800(CST)
为啥子?莫非单纯访问文件也会触发回调?
Ifyouwanttobenotifiedwhenthefilewasmodified,notjustaccessed,youneedtocomparecurr.mtimeandprev.mtime.
在v0.10之后的改动。如果监听的文件不存在,会怎么处理。如下
Note:whenanfs.watchFileoperationresultsinanENOENTerror,itwillinvokethelisteneronce,withallthefieldszeroed(or,fordates,theUnixEpoch).InWindows,blksizeandblocksfieldswillbeundefined,insteadofzero.Ifthefileiscreatedlateron,thelistenerwillbecalledagain,withthelateststatobjects.Thisisachangeinfunctionalitysincev0.10.
fs.watch()
fs.watch(filename[,options][,listener])fs.unwatchFile(filename[,listener])
这接口非常不靠谱(当前测试用的v6.1.0),参考https://github.com/nodejs/node/issues/7420
fs.watch(filename[,options][,listener])#
注意:fs.watch()这个接口并不是在所有的平台行为都一致,并且在某些情况下是不可用的。recursive这个选项只在mac、windows下可用。
问题来了:
- 不一致的表现。
- 不可用的场景。
- linux上要recursive咋整。
Thefs.watchAPIisnot100%consistentacrossplatforms,andisunavailableinsomesituations.TherecursiveoptionisonlysupportedonOSXandWindows.
备忘,不可用的场景。比如网络文件系统等。
Forexample,watchingfilesordirectoriescanbeunreliable,andinsomecasesimpossible,onnetworkfilesystems(NFS,SMB,etc),orhostfilesystemswhenusingvirtualizationsoftwaresuchasVagrant,Docker,etc.
另外,listener回调有两个参数,分别是event、filename。其中,filename仅在linux、windows上会提供,并且不是100%提供,所以,尽量不要依赖filename。
在linux、osx上,fs.watch()监听的是inode。如果文件被删除,并重新创建,那么删除事件会触发。同时,fs.watch()监听的还是最初的inode。(API的设计就是这样的)
结论:怎么看都感觉这个API很不靠谱,虽然性能比fs.watchFile()要高很多。
先来个例子,在osx下测试了一下,简直令人绝望。。。无论是创建、修改、删除文件,evt都是rename。。。
varfs=require('fs'); varoptions={ persistent:true, recursive:true, encoding:'utf8' }; fs.watch('../',options,function(event,filename){ console.log('触发事件:'+event); if(filename){ console.log('文件名是:'+filename); }else{ console.log('文件名是没有提供'); } });
修改下fileForWatch.txt,看到下面输出。。。感觉打死也不想用这个API。。。
贴下环境:osx10.11.4,nodev6.1.0。
触发事件:rename
文件名是:fs/fileForWatch.txt___jb_bak___
触发事件:rename
文件名是:fs/fileForWatch.txt
触发事件:rename
文件名是:fs/fileForWatch.txt___jb_old___
触发事件:rename
文件名是:.idea/workspace.xml___jb_bak___
触发事件:rename
文件名是:.idea/workspace.xml
触发事件:rename
文件名是:.idea/workspace.xml___jb_old___
参考linux命令行,不举例子了。。。
fs.chown(path,uid,gid,callback)fs.chownSync(path,uid,gid)fs.fchown(fd,uid,gid,callback)fs.fchownSync(fd,uid,gid)
可以用fs.chmod(),也可以用fs.fchmod()。两者的区别在于,前面传的是文件路径,后面传的的文件句柄。
- fs.chmod)、fs.fchmod()区别:传的是文件路径,还是文件句柄。
- fs.chmod()、fs.lchmod()区别:如果文件是软连接,那么fs.chmod()修改的是软连接指向的目标文件;fs.lchmod()修改的是软连接。
fs.chmod(path,mode,callback)fs.chmodSync(path,mode)
fs.fchmod(fd,mode,callback)fs.fchmodSync(fd,mode)
fs.lchmod(path,mode,callback)#fs.lchmodSync(path,mode)
例子:
varfs=require('fs'); fs.chmod('./fileForChown.txt','777',function(err){ if(err)console.log(err); console.log('权限修改成功'); });
同步版本:
varfs=require('fs'); fs.chmodSync('./fileForChown.txt','777');
区别:
- fs.stat()vsfs.fstat():传文件路径vs文件句柄。
- fs.stat()vsfs.lstat():如果文件是软链接,那么fs.stat()返回目标文件的状态,fs.lstat()返回软链接本身的状态。
fs.stat(path,callback)fs.statSync(path)
fs.fstat(fd,callback)fs.fstatSync(fd)
fs.lstat(path,callback)fs.lstatSync(path)
主要关注Class:fs.Stats。
首先是方法
- stats.isFile()--是否文件
- stats.isDirectory()--是否目录
- stats.isBlockDevice()--什么鬼
- stats.isCharacterDevice()--什么鬼
- stats.isSymbolicLink()(onlyvalidwithfs.lstat())--什么鬼
- stats.isFIFO()--什么鬼
- stats.isSocket()--是不是socket文件
官网例子:
{ dev:2114, ino:48064969, mode:33188, nlink:1, uid:85, gid:100, rdev:0, size:527, blksize:4096, blocks:8, atime:Mon,10Oct201123:24:11GMT,//访问时间 mtime:Mon,10Oct201123:24:11GMT,//文件内容修改时间 ctime:Mon,10Oct201123:24:11GMT,//文件状态修改时间 birthtime:Mon,10Oct201123:24:11GMT//创建时间 }
- atime:AccessTime//访问时间
- mtime::ModifiedTime//文件内容修改时间
- ctime:ChangedTime.//文件状态修改时间,比如修改文件所有者、修改权限、重命名等
- birthtime:BirthTime//创建时间。在某些系统上是不可靠的,因为拿不到。
例子:
varfs=require('fs'); vargetTimeDesc=function(d){ return[d.getFullYear(),d.getMonth()+1,d.getDate()].join('-')+''+[d.getHours(),d.getMinutes(),d.getSeconds()].join(':'); }; fs.stat('./fileForStat.txt',function(err,stats){ console.log('文件大小:'+stats.size); console.log('创建时间:'+getTimeDesc(stats.birthtime)); console.log('访问时间:'+getTimeDesc(stats.atime)); console.log('修改时间:'+getTimeDesc(stats.mtime)); });
输出如下:
/usr/local/bin/nodestat.js
文件大小:3613
创建时间:2016-7-1612:40:49
访问时间:2016-7-1612:40:49
修改时间:2016-7-1612:40:49Processfinishedwithexitcode0
同步的例子:
varfs=require('fs'); vargetTimeDesc=function(d){ return[d.getFullYear(),d.getMonth()+1,d.getDate()].join('-')+''+[d.getHours(),d.getMinutes(),d.getSeconds()].join(':'); }; varstats=fs.statSync('./fileForStat.txt'); console.log('文件大小:'+stats.size); console.log('创建时间:'+getTimeDesc(stats.birthtime)); console.log('访问时间:'+getTimeDesc(stats.atime)); console.log('修改时间:'+getTimeDesc(stats.mtime));
例子:
//fs.access(path[,mode],callback) varfs=require('fs'); fs.access('./fileForAccess.txt',function(err){ if(err)throwerr; console.log('可以访问'); });
同步版本:
//fs.accessSync(path[,mode]) varfs=require('fs'); //如果成功,则返回undefined,如果失败,则抛出错误(什么鬼) try{ fs.accessSync('./fileForAccess.txt'); }catch(e){ throw(e); }
比较底层的接口,实际需要用到的机会不多。需要用到的时候看下文档就行。
- flags:文件打开模式,比如r、r+、w、w+等。可选模式非常多。
- mode:默认是666,可读+可写。
fs.open(path,flags[,mode],callback)fs.openSync(path,flags[,mode])fs.close(fd,callback)fs.closeSync(fd)
相对底层的读取接口,参数如下
- fd:文件句柄。
- buffer:将读取的文件内容写到buffer里。
- offset:buffer开始写入的位置。(在offset开始写入,还是offset+1?)
- length:要读取的字节数。
- position:文件从哪个位置开始读取。如果是null,那么就从当前位置开始读取。(读取操作会记录下上一个位置)
此外,callback的回调参数为(err,bytesRead,buffer)
fs.read(fd,buffer,offset,length,position,callback)
fs.appendFile(file,data[,options],callback)
- file:可以是文件路径,也可以是文件句柄。(还可以是buffer?)
- data:要追加的内容。string或者buffer。
- options
- encoding:编码,默认是utf8
- mode:默认是0o666
- flag:默认是a
注意:如果file是文件句柄,那么
- 开始追加数据前,file需要已经打开。
- file需要手动关闭。
varfs=require('fs'); fs.appendFile('./extra/fileForAppend.txt','helo','utf8',function(err){ if(err)throwerr; console.log('append成功'); });
fs.truncate(path,len,callback)fs.truncateSync(path,len)
fs.ftruncate(fd,len,callback)fs.ftruncateSync(fd,len)
用途参考linux说明文档。
要点:
- offset不会变化。比如通过fs.read()读取文件内容,就需要特别注意。
- 如果len小于文件内容长度,剩余文件内容部分会丢失;如果len大于文件内容长度,那么超出的部分,会用\0进行填充。
- 如果传的是文件路径,需要确保文件是可写的;如果传的是文件句柄,需要确保文件句柄已经打开并且可写入。
Thetruncate()andftruncate()functionscausetheregularfilenamedbypathorreferencedbyfdtobetruncatedtoasizeofpreciselylengthbytes.
Ifthefilepreviouslywaslargerthanthissize,theextradataislost.Ifthefilepreviouslywasshorter,itisextended,andtheextendedpartreadsasnullbytes('\0').
Thefileoffsetisnotchanged.
Withftruncate(),thefilemustbeopenforwriting;withtruncate(),thefilemustbewritable.
- path/fd:文件路径/文件句柄
- atime:AccessTime。上一次访问文件数据的时间。
- mtime:ModifiedTime。修改时间。
fs.utimes(path,atime,mtime,callback)fs.utimesSync(path,atime,mtime)
fs.futimes(fd,atime,mtime,callback)fs.futimesSync(fd,atime,mtime)
备注,在命令行下可以
- 通过stat查看文件的状态信息,包括了上面的atime、mtime。
- 通过touch修改这几个时间。
fs.symlink(target,path[,type],callback)fs.symlinkSync(target,path[,type])
fs.link(srcpath,dstpath,callback)fs.linkSync(srcpath,dstpath)
link()createsanewlink(alsoknownasahardlink)toanexistingfile.
软链接、硬链接区别:参考或者[这个]。(https://www.nhooo.com/article/96135.htm)
- 硬链接:inode相同,多个别名。删除一个硬链接文件,不会影响其他有相同inode的文件。
- 软链接:有自己的inode,用户数据块存放指向文件的inode。
参考这里。
fs.mkdtemp(prefix,callback)fs.mkdtempSync(prefix)
备忘:跟普通的随便找个目录,创建个随机名字的文件夹,有什么区别?
代码示例如下:
varfs=require('fs'); fs.mkdtemp('/tmp/',function(err,folder){ if(err)throwerr; console.log('创建临时目录:'+folder); });
输出如下:
/usr/local/bin/nodemkdtemp.js
创建临时目录:/tmp/Cxw51O
找出软连接指向的真实路径
fs.readlink(path[,options],callback)fs.readlinkSync(path[,options])
如下面例子,创建了个软链接指向fileForReadLink.txt,通过fs.readlink()就可以找出原始的路径。
varfs=require('fs'); varrandomFileName='./extra/fileForReadLink-'+String(Math.random()).slice(2,6)+'.txt'; fs.symlinkSync('./extra/fileForReadLink.txt',randomFileName); fs.readlink(randomFileName,'utf8',function(err,linkString){ if(err)throwerr; console.log('链接文件内容:'+linkString); });
类似终端下直接运行readlink。对于软链接文件,效果同上面代码。对于硬链接,没有输出。
➜ extragit:(master)✗readlinkfileForReadLink-9827.txt
./extra/fileForReadLink.txt
➜ extragit:(master)✗readlinkfileForLinkHard.txt
➜ extragit:(master)✗readlinkfileForLinkSoft.txt
./extra/fileForLink.txt
fs.realpath(path[,options],callback)fs.realpathSync(path[,options])
例子:(不能作用于软链接?)
varfs=require('fs'); varpath=require('path'); //fileForRealPath1.txt是普通文件,正常运行 fs.realpath('./extra/inner/fileForRealPath1.txt',function(err,resolvedPath){ if(err)throwerr; console.log('fs.realpath:'+resolvedPath); }); //fileForRealPath.txt是软链接,会报错,提示找不到文件 fs.realpath('./extra/inner/fileForRealPath.txt',function(err,resolvedPath){ if(err)throwerr; console.log('fs.realpath:'+resolvedPath); }); console.log('path.resolve:'+path.resolve('./extra/inner/fileForRealpath.txt'));
输出如下:
path.resolve:/Users/a/Documents/git-code/git-blog/demo/2015.05.21-node-basic/fs/extra/inner/fileForRealpath.txt
fs.realpath:/Users/a/Documents/git-code/git-blog/demo/2015.05.21-node-basic/fs/extra/inner/fileForRealPath1.txt
/Users/a/Documents/git-code/git-blog/demo/2015.05.21-node-basic/fs/realpath.js:12
if(err)throwerr;
^
Error:ENOENT:nosuchfileordirectory,realpath'./extra/inner/fileForRealPath.txt'
atError(native)
Processfinishedwithexitcode1
fs.rmdir(path,callback)fs.rmdirSync(path)
例子如下:
varfs=require('fs'); fs.rmdir('./dirForRemove',function(err){ if(err)throwerr; console.log('目录删除成功'); });
缓冲区内容写到磁盘
fs.fdatasync(fd,callback)fs.fdatasyncSync(fd)
可以参考这里:
1、sync函数sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。通常称为update的系统守护进程会周期性地(一般每隔30秒)调用sync函数。这就保证了定期冲洗内核的块缓冲区。命令sync(1)也调用sync函数。
2、fsync函数fsync函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。fsync可用于数据库这样的应用程序,这种应用程序需要确保将修改过的块立即写到磁盘上。
3、fdatasync函数fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。对于提供事务支持的数据库,在事务提交时,都要确保事务日志(包含该事务所有的修改操作以及一个提交记录)完全写到硬盘上,才认定事务提交成功并返回给应用层。
待确认
- 通篇的mode,待确认。
- fs.access()更多用法(涉及fs.constants.F_OK等权限)
希望本文所述对大家node.js程序设计有所帮助。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。