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');
                // => ../../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 }

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

2.stats.isFile() —— 如果是个标准文件,而不是目录,socket,符号链接或者设备,则返回true,否则false
3.stats.isDiretory() —— 如果是目录则返回tue,否则false
4.stats.isBlockDevice() —— 如果是块设备则返回true,在大多数UNIX系统中块设备通常都在/dev目录下
5.stats.isChracterDevice() —— 如果是字符设备返回true
6.stats.isSymbolickLink() —— 如果是文件链接返回true
7.stats.isFifo() —— 如果是个FIFO(UNIX命名管道的一个特殊类型)返回true
8.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页)

1.r —— 以只读方式打开文件,数据流的初始位置在文件开始
2.r —— 以可读写方式打开文件,数据流的初始位置在文件开始
3.w ——如果文件存在,则将文件长度清0,即该文件内容会丢失。如果不存在,则尝试创建它。数据流的初始位置在文件开始
4.w —— 以可读写方式打开文件,如果文件不存在,则尝试创建它,如果文件存在,则将文件长度清0,即该文件内容会丢失。数据流的初始位置在文件开始
5.a —— 以只写方式打开文件,如果文件不存在,则尝试创建它,数据流的初始位置在文件末尾,随后的每次写操作都会将数据追加到文件后面。
6.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行),当下面三种情况发生时,它会被调用:

1.有错误发生
2.成功读取了数据
3.没有数据可读

如果有错误发生,第一个参数(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)向文件写入数据。缓冲区对象需要附带几个信息一起做为参数:

1.缓冲区的数据
2.待写数据从缓冲区的什么位置开始
3.待写数据的长度
4.数据写到文件的哪个位置
5.当操作结束后被调用的回调函数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关闭文件。

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

Node.js文件操作详解的更多相关文章

  1. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  3. Node.js调试技术总结分享

    Node.js是一个可以快速构建网络服务及应用的平台。该平台的构建是基于Chrome's JavaScript runtime,也就是说,实际上它是对Google V8引擎(应用于Google Chrome浏览器)进行了封装。 今天介绍Node.js调式目前有几种技术,需要的朋友可以参考下。

  4. node.js实现http服务器与浏览器之间的内容缓存操作示例

    这篇文章主要介绍了node.js实现http服务器与浏览器之间的内容缓存操作,结合实例形式分析了node.js http服务器与浏览器之间的内容缓存原理与具体实现技巧,需要的朋友可以参考下

  5. 教你如何使用node.js制作代理服务器

    本文介绍了如何使用node.js制作代理服务器,图文并茂,十分的详细,代码很简洁易懂,这里推荐给大家。

  6. node.js中的fs.openSync方法使用说明

    这篇文章主要介绍了node.js中的fs.openSync方法使用说明,本文介绍了fs.openSync方法说明、语法、接收参数、使用实例和实现源码,需要的朋友可以参考下

  7. Node.js+ELK日志规范的实现

    这篇文章主要介绍了Node.js+ELK日志规范的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  8. node.js爬虫框架node-crawler初体验

    这篇文章主要介绍了node.js爬虫框架node-crawler的相关资料,帮助大家利用node.js进行爬虫,感兴趣的朋友可以了解下

  9. node.js中的fs.existsSync方法使用说明

    这篇文章主要介绍了node.js中的fs.existsSync方法使用说明,本文介绍了fs.existsSync方法说明、语法、接收参数、使用实例和实现源码,需要的朋友可以参考下

  10. 说说如何利用 Node.js 代理解决跨域问题

    这篇文章主要介绍了Node.js代理解决跨域问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部