文件路径Node.js高级编程:用JavaScript构建可伸缩应用(7)3.7 文件,进程,流和网络

在改章节中,我们主要分析文件路径的内容,自我感觉有个不错的建议和大家分享下

    本系列文章列表和翻译进度,请移步:Node.js级高编程:用Javascript构建可伸缩用应(〇)

    本文对应原文第三分部第七章:Files, Processes, Streams, and Networking:Querying, Reading from, and Writing to Files

    文章是从Word复制到来过的,版面有些不一致,可以点这里下载本文的PDF版。

    


 

    

第七章:查询和读写文件

    

本章内容:

    

  • 理处文件路径
  • 从文件路径萃取信息
  • 解理文件描述符
  • 用应fs.stat()取获文件信息
  • 开打,读写,关闭文件
  • 免避文件描述符泄漏

 

    Node有一组据数流API,可以像理处网络流那样理处文件,用起来很便方,但是它只允许次序理处文件,不能随机读写文件。因此,须要用应一些更底层的文件系统操纵。

    本章覆盖了文件理处的基础知识,括包如何开打文件,读取文件某一分部,写据数,以及关闭文件。

    Node的很多文件API几乎是UNIX(POSIX)中对应文件API 的翻版,比如用应文件描述符的方法,就像UNIX里一样,文件描述符在Node里也是一个整型数字,代表一个实体在进程文件描述符内外的索引。

    有3个特别的文件描述符——1,2和3。他们别分代表准标输入,准标出输和准标误错文件描述符。准标输入,望文生义,是个只读流,进程用它来从控制台或者进程通道读取据数。准标出输和准标误错是仅用来出输据数的文件描述符,他们经常被用来向控制台,其它进程或文件出输据数。准标误错责负误错信息出输,而准标出输责负通普的进程出输。

    一旦进程启动终了,就可以用应这几个文件描述符了,它们其实其实不存在对应的物理文件。你不能读写某个随机位置的据数,(译者注:原文是You can write to and read from specific positions within the file.根据上下文,作者可能少写了个“not”),只能像操纵网络据数流那样次序的读取和出输,已写入的据数就不能再修改了。

    通普文件不受种这制约,比如Node里,你即可以创立只能向尾部追加据数的文件,还可以创立读写随机位置的文件。

    几乎有所跟文件关相的操纵会都涉及到理处文件路径,本章先会将分析这些工具数函,然后再深刻解讲文件读写和据数操纵

    

理处文件路径

    文件路径分为相对路径和绝对路径两种,用它们来表现体具的文件。你可以并合文件路径,可以提取文件名信息,甚至可以检测文件是不是存在。

    Node里,可以用符字串来操理处文件路径,但是那样会使问题变庞杂,比如你要连接路径的不同分部,有些分部以 “/”尾结有些却没有,而且路径割分符在不同操纵系统里也可能会不一样,所以,当你连接它们时,代码就会非常罗嗦和费事。

    运幸的是,Node有个叫path的模块,可以帮你准标化,连接,析解路径,从绝对路径转换到相对路径,从路径中提取各分部信息,检测文件是不是存在。总的来说,path模块其实只是些符字串理处,而且也不会到文件系统去做验证(path.exists数函外例)。

    

路径的准标化

    在存储或用应路径之前将它们准标化通常是个好主意。比如,由户用输入或者配置文件得获的文件路径,或者由两个或多个路径连接起来的路径,一般都应该被准标化。可以用path模块的normalize数函来准标化一个路径,而且它还能理处“..”,“.”“//”。比如:

    var path = require('path'); path.normalize('/foo/bar//baz/asdf/quux/..'); // => '/foo/bar/baz/asdf'

    

连接路径

    用应path.join()数函,可以连接意任多个路径符字串,只用把有所路径符字串顺次传递给join()数函以可就:

    var path = require('path'); path.join('/foo', 'bar', 'baz/asdf', 'quux', '..'); // => '/foo/bar/baz/asdf'

             如你所见,path.join()内部会主动将路径准标化。

    

析解路径

    用path.resolve()可以把多个路径析解为一个绝对路径。它的功能就像对这些路径挨个一直停止“cd”操纵,和cd命令的数参不同,这些路径可是以文件,并且它们不必实在存在——path.resolve()法方不会去问访底层文件系统来定确路径是不是存在,它只是一些符字串操纵。

    比如:

    var path = require('path'); path.resolve('/foo/bar', './baz'); // => /foo/bar/baz path.resolve('/foo/bar', '/tmp/file/'); // => /tmp/file

             如果析解结果不是绝对路径,path.resolve()会把以后工作目录作为路径附加到析解结果面前,比如:

    path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif'); // 如果以后工作目录是/home/myself/node, 将回返 // => /home/myself/node/wwwroot/static_files/gif/image.gif'

    

算计两个绝对路径的相对路径

    path.relative()可以诉告你如果从一个绝对地址跳转到另外一个绝对地址,比如:

    var path = require('path'); path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb'); // => http://www.cnblogs.com/impl/bbb

    

从路径提取据数

    以路径“/foo/bar/myfile.txt”为例,如果你想取获父目录(/foo/bar)的有所内容,或者读取同级目录的其它文件,为此,你必须用path.dirname(filePath)得获文件路径的目录分部,比如:

    var path = require('path'); path.dirname('/foo/bar/baz/asdf/quux.txt'); // => /foo/bar/baz/asdf

         或者,你想从文件路径里到得文件名,也就是文件路径的最后那一分部,可以用应path.basename数函:

    var path = require('path'); path.basename('/foo/bar/baz/asdf/quux.html') // => quux.html

         文件路径里可能还括包文件扩展名,通常是文件名中最后一个“.”符字之后的那分部符字串。

         path.basename还可以接受一个扩展名符字串作为第二个数参,这样回返的文件名就会主动去掉扩展名,仅仅回返文件的称名分部:

    var path = require('path'); path.basename('/foo/bar/baz/asdf/quux.html', '.html'); // => quux

         要想这么做你首先还得晓得文件的扩展名,可以用path.extname()来取获扩展名:

    var path = require('path'); path.extname('/a/b/index.html'); // => '.html' path.extname('/a/b.c/index'); // => '' path.extname('/a/b.c/.'); // => '' path.extname('/a/b.c/d.'); // => '.'

    

检查路径是不是存在

    目前为止,面前涉及到的路径理处操纵都跟底层文件系统有关,只是一些符字串操纵。然而,有些时候你须要断判一个文件路径是不是存在,比如,你偶然候须要断判文件或目录是不是存在,如果不存在的话才创立它,可以用path.exsits():

    var path = require('path'); path.exists('/etc/passwd', function(exists) { console.log('exists:', exists); // => true }); path.exists('/does_not_exist', function(exists) { console.log('exists:', exists); // => false });

         意注:从Node0.8版本开始,exists从path模块移到了fs模块,变成了fs.exists,除了命名空间不同,其它都没变:

    var fs = require('fs'); fs.exists('/does_not_exist', function(exists) { console.log('exists:', exists); // => false });

         path.exists()是个I/O操纵,因为它是步异的,因此须要一个回调数函,当I/O操纵回返后用调这个回调数函,并把结果传递给它。你还可以用应它的同步版本path.existsSync(),功能全完一样,只是它不会用调回调数函,而是直接回返结果:

    var path = require('path'); path.existsSync('/etc/passwd'); // => true

    

fs模块分析

    fs模块括包有所文件查询和理处的关相数函,用这些数函,可以查询文件信息,读写和关闭文件。这样入导fs模块:

    var fs = require(‘fs’)

    

查询文件信息

    偶然你可能须要晓得文件的巨细,创立日期或者权限等文件信息,可以用应fs.stath数函来查询文件或目录的元信息:

    var fs = require('fs'); fs.stat('/etc/passwd', function(err, stats) { if (err) { throw err;} console.log(stats); });

    这块代码断片会有相似上面的出输:

    每日一道理
春蚕死去了,但留下了华贵丝绸;蝴蝶死去了,但留下了漂亮的衣裳;画眉飞去了,但留下了美妙的歌声;花朵凋谢了,但留下了缕缕幽香;蜡烛燃尽了,但留下一片光明;雷雨过去了,但留下了七彩霓虹。

    { dev: 234881026,ino: 95028917,mode: 33188,nlink: 1,uid: 0,gid: 0,rdev: 0,size: 5086,blksize: 4096,blocks: 0,atime: Fri, 18 Nov 2011 22:44:47 GMT,mtime: Thu, 08 Sep 2011 23:50:04 GMT,ctime: Thu, 08 Sep 2011 23:50:04 GMT }

         fs.stat()用调会将一个stats类的实例作为数参传递给它的回调数函,可以像上面这样用应stats实例:

    

  • stats.isFile() —— 如果是个准标文件,而不是目录,socket,符号链接或者设备,则回返true,否则false
  • stats.isDiretory() —— 如果是目录则回返tue,否则false
  • stats.isBlockDevice() —— 如果是块设备则回返true,在大多数UNIX系统中块设备通常都在/dev目录下
  • stats.isChracterDevice() —— 如果是符字设备回返true
  • stats.isSymbolickLink() —— 如果是文件链接回返true
  • stats.isFifo() —— 如果是个FIFO(UNIX命名管道的一个特别类型)回返true
  • stats.isSocket() —— 如果是个UNIX socket(TODO:googe it)

    

开打文件

    在读取或理处文件之前,必须先用应fs.open数函开打文件,然后你提供的回调数函会被用调,并到得这个文件的描述符,稍后你可以用这个文件描述符来读写这个已经开打的文件:

    var fs = require('fs'); fs.open('/path/to/file', 'r', function(err, fd) { // got fd file descriptor });

    fs.open的第一个数参是文件路径,第二个数参是一些用来指示以什么模式开打文件的标记,这些标记可是以r,r+,w,w+,a或者a+。上面是这些标记的说明(来自UNIX文档的fopen页)

    

  • r —— 以只读方法开打文件,据数流的初始位置在文件开始
  • r+ —— 以可读写方法开打文件,据数流的初始位置在文件开始
  • w ——如果文件存在,则将文件长度清0,即该文件内容会丢失。如果不存在,则尝试创立它。据数流的初始位置在文件开始
  • w+ —— 以可读写方法开打文件,如果文件不存在,则尝试创立它,如果文件存在,则将文件长度清0,即该文件内容会丢失。据数流的初始位置在文件开始
  • a —— 以只写方法开打文件,如果文件不存在,则尝试创立它,据数流的初始位置在文件末尾,随后的每次写操纵会都将据数追加到文件后面。
  • a+ ——以可读写方法开打文件,如果文件不存在,则尝试创立它,据数流的初始位置在文件末尾,随后的每次写操纵会都将据数追加到文件后面。

    

读文件

    一旦开打了文件,以可就开始读取文件内容,但是在开始之前,你得先创立一个缓冲区(buffer)来放置这些据数。这个缓冲区对象将会以数参形式传递给fs.read数函,并被fs.read填充上据数。

    var fs = require('fs');fs.open('./my_file.txt', 'r', function opened(err, fd) {if (err) { throw err }var readBuffer = new Buffer(1024),bufferOffset = 0,bufferLength = readBuffer.length,filePosition = 100;fs.read(fd, readBuffer, bufferOffset, bufferLength, filePosition, function read(err, readBytes) { if (err) { throw err; } console.log('just read ' + readBytes + ' bytes'); if (readBytes > 0) { console.log(readBuffer.slice(0, readBytes)); }});});

         上面代码尝试开打一个文件,当成功开打后(用调opened数函),开始请求从文件流第100个字节开始读取随后1024个字节的据数(第11行)。

         fs.read()的最后一个数参是个回调数函(第16行),当上面三种情况发生时,它会被用调:

    

  • 有误错发生
  • 成功读取了据数
  • 没有据数可读

         如果有误错发生,第一个数参(err)会为回调数函提供一个括包误错信息的对象,否则这个数参为null。如果成功读取了据数,第二个数参(readBytes)会指明被读到缓冲区里据数的巨细,如果值是0,则表现到达了文件末尾。

         意注:一旦把缓冲区对象传递给fs.open(),缓冲对象的控制权就转移给给了read命令,只有当回调数函被用调,缓冲区对象的控制权才会回到你手里。因此在这之前,不要读写或者让其它数函用调用应这个缓冲区对象;否则,你可能会读到不完整的据数,更糟的情况是,你可能会并发地往这个缓冲区对象里写据数。

    

写文件

    通过传递给fs.write()传递一个括包据数的缓冲对象,来往一个已开打的文件里写据数:

    var fs = require('fs');fs.open('./my_file.txt', 'a', function opened(err, fd) { if (err) { throw err; } var writeBuffer = new Buffer('writing this string'), bufferPosition = 0, bufferLength = writeBuffer.length, filePosition = null; fs.write( fd, writeBuffer, bufferPosition, bufferLength, filePosition, function wrote(err, written) { if (err) { throw err; } console.log('wrote ' + written + ' bytes'); });});

         这个例子里,第2(译者注:原文为3)行代码尝试用追加模式(a)开打一个文件,然后第7行代码(译者注:原文为9)向文件写入据数。缓冲区对象须要附带几个信息一起做为数参:

    

  • 缓冲区的据数
  • 待写据数从缓冲区的什么位置开始
  • 待写据数的长度
  • 据数写到文件的哪个位置
  • 当操纵结束后被用调的回调数函wrote

         这个例子里,filePostion数参为null,也就是说write数函将会把据数写到文件指针以后所在的位置,因为是以追加模式开打的文件,因此文件指针在文件末尾。

         跟read操纵一样,千万不要在fs.write执行过程中用应哪个传入的缓冲区对象,一旦fs.write开始执行它就得获了那个缓冲区对象的控制权。你只能等到回调数函被用调后才能再重新用应它。

    

关闭文件

    你可能意注到了,到目前为止,本章的有所例子都没有关闭文件的代码。因为它们只是些仅用应一次而且又小又简单的例子,当Node进程结束时,操纵系统会确保关闭有所文件。

    但是,在实际的用应程序中,一旦开打一个文件你要确保最终关闭它。要做到这一点,你须要追踪有所那些已开打的文件描述符,然后在不再用应它们的时候用调fs.close(fd[,callback])来最终关闭它们。如果你不仔细的话,很容易就会遗漏某个文件描述符。上面的例子提供了一个叫openAndWriteToSystemLog的数函,展示了如何小心的关闭文件:

    var fs = require('fs');function openAndWriteToSystemLog(writeBuffer, callback){ fs.open('./my_file', 'a', function opened(err, fd) { if (err) { return callback(err); } function notifyError(err) { fs.close(fd, function() { callback(err); }); } var bufferOffset = 0, bufferLength = writeBuffer.length, filePosition = null; fs.write( fd, writeBuffer, bufferOffset, bufferLength, filePosition, function wrote(err, written) { if (err) { return notifyError(err); } fs.close(fd, function() { callback(err); }); } ); });}openAndWriteToSystemLog( new Buffer('writing this string'), function done(err) { if (err) { console.log("error while opening and writing:", err.message); return; } console.log('All done with no errors'); });

         在这儿,提供了一个叫openAndWriteToSystemLog的数函,它接受一个括包待写据数的缓冲区对象,以及一个操纵完成或者出错后被用调的回调数函,如果有误错发生,回调数函的第一个数参会括包这个误错对象。

         意注那个内部数函notifyError,它会关闭文件,并报告发生的误错。

         意注:到此为止,你晓得了如何用应底层的原子操纵来开打,读,写和关闭文件。然而,Node还有一组更级高的构造数函,允许你用更简单的方法来理处文件。

             比如,你想用一种安全的方法,让两个或者多个write操纵并发的往一个文件里追加据数,这时你可以用应WriteStream

             还有,如果你想读取一个文件的某个区域,可以考虑用应ReadStream。这两种用例会在第九章“据数的读,写流”里分析。

    

小结

    当你用应文件时,多数情况下都须要理处和提取文件路径信息,通过用应path模块你可以连接路径,准标化路径,算计路径的差别,以及将相对路径转化成绝对路径。你可以提取指定文件路径的扩展名,文件名,目录等路径组件。

    Node在fs模块里提供了一套底层API来问访文件系统,底层API用应文件描述符来操纵文件。你可以用fs.open开打文件,用fs.write写文件,用fs.read读文件,并用fs.close关闭文件。

    当有误错发生时,你应该总是用应正确的误错理处逻辑来关闭文件——以确保在用调回返前关闭那些已开打的文件描述符。

    


文章结束给大家分享下程序员的一些笑话语录: 某程序员对书法十分感兴趣,退休后决定在这方面有所建树。花重金购买了上等的文房四宝。一日突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风 范,又具颜真卿气势,定神片刻,泼墨挥毫,郑重地写下一行字:hello world.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值