详解nodejs 文本操作模块-fs模块(二)


Posted in NodeJs onDecember 22, 2016

前一篇学习了文件的打开和关闭,文件操作总不能只包含打开和关闭吧,这里就开始文件的读写操作。

fs模块方法

1:read和readSync方法

该方法,是从文件的指定位置处读取文件,一直读取到文件底部,然后江都区到的内容输出到一个缓存区,使用方法如下:

fs.read(fd,buffer,offset,length,position,callback);

在read方法中,支持6个参数:

  • fd参数,是文件描述符,是open方法的回调函数中获取到的,是一个数字。
  • buffer,是一个buffer对象,用于指定将文件数据读取到那个缓存区,如果不定义,则会生成一个新的缓存区,进行存放新读取到的数据。
  • offset,是一个整数值,用于指定向缓存区中写入数据时的开始位置,以字节为单位。其实也就是,读入到缓存中的数据,从buffer对象的第几个元素开始写入。
  • length,是一个整数值,表示读入的数据,多少数据写入到buffer对象中去,要保证不能超出buffer的容纳范围,否则会抛出一个范围异常。
  • position,是一个整数值,表示,从文件中的哪个位置,开始读取数据,如果设置为非0的整数,则从该整数所示的位置,读取长度为length的数据到buffer对象中。
  • callback,回调函数,当读取文件成功之后,把执行该函数,该回调函数支持三个参数:
function (err,bytesRead,buffer){ 
 //err为读取文件操作失败时,触发的错误对象 
 //bytesRead为读取到的字节数,如果文件的比较大,则该值就是length的值, 
 //如果文件的大小比length小,则该值为实际中读取到的字节数。 
 //buffer为读取到的内容,保存到了该缓存区,如果在使用read时, 
 //传入了buffer对象,则此处的buffer就是传入的buffer对象。 
 //如果在read时没有传入buffer,则此处的buffer为新创建的buffer对象 
}

上面把参数的含义以及回调函数的定义,都说明了一下,这里就看一个示例吧:

var fs = require("fs"); 
 
fs.open("fs.txt","r",function(err,fd){ 
 //读取fs.text,文件的内容为“123456789”,长度为9 
 var buffer = new Buffer([0,0,0,0,0,0,0,0,0,0]); 
 //创建一个长度为10,初始值为0的buffer对象。 
 //数据比较少,就直接写了,否则还是使用fill方法吧。 
 console.log(buffer); 
 //<Buffer 00 00 00 00 00 00 00 00 00 00> 
 //初始时的buffer对象 
  
 fs.read(fd,buffer,4,6,4,function(err,bytesRead,buffer1){ 
  //读取到的数据,从buffer对象的第5个元素开始保存,保存6个字节的元素 
  //读取文件,是从文件的第5个字节开始,因为文件中内容长度为9, 
  //那么,读取到的内容就是56789,所以buffer的最后一位仍然为初始值。 
  //由于想要读取的字节长度为6,但是文件内容过短,只读取了5个字节的有效数据 
  //就到了文件的结尾了,所以,bytesRead的值不是6,而是5。 
  //而buffer对象,为被写入新数据之后的对象。 
  console.log(bytesRead); //5 
    console.log(buffer1);  
  //<Buffer 00 00 00 00 35 36 37 38 39 00> 
    console.log(buffer); 
  //<Buffer 00 00 00 00 35 36 37 38 39 00> 
  //它们俩是完全相同的。其实质是,它们俩占据的内存也是相同的, 
  //它们就是同一个缓存区。 
 }); 
});

一般情况下,异步调用时,回调函数中,只有两个参数存在,第一个参数为err对象,第二个参数为操作之后的数据,可是,这里有三个数据,那么在同步时,什么才是返回值呢?

所以,要做如下的测试:

var fs = require("fs"); 
 
fs.open("fs.txt","r",function(err,fd){ 
 //读取fs.text,文件的内容为“123456789”,长度为9 
 var buffer = new Buffer([0,0,0,0,0,0,0,0,0,0]); 
  
 var bytesRead = fs.readSync(fd,buffer,4,6,4); 
 console.log(bytesRead); 
});

返回的是bytesRead的值,并没有返回buffer对象,可以想象,因为buffer对象是原本传入的buffer对象,依然可以通过传入的buffer对象,直接访问到重写数据之后的buffer对象。

但是,有个问题就来了,如果没有传入buffer对象呢?这又要如何呢?这个问题暂且别过,因为这个问题,并没有在一些API文档中说明,在书中也没有看到这个用法,但是接下来,我们去分析一下源码,就能发现,除了上述的两种常用的方法之外,还有其他的使用方式。

OK,先看下read方法的源码:

fs.read = function(fd, buffer, offset, length, position, callback) { 
 if (!util.isBuffer(buffer)) { 
 //如果传入的第二个参数不是一个buffer对象,则做一些自适应的处理 
 // legacy string interface (fd, length, position, encoding, callback) 
 var cb = arguments[4], 
  encoding = arguments[3]; 
 //本来read方法是有6个参数的,当buffer没有传入的时候, 
 //则相应的offset也变得没有意义,所以变为了4个参数。 
 //而这个时候,参数的形式就变成了前面英文部分的样子。5个参数,加入了encoding参数。 
  
 assertEncoding(encoding); 
 //判断传入的encoding是否是当前支持的编码方式 
 //如果不是,则抛出异常 
 
 position = arguments[2]; 
 length = arguments[1]; 
 buffer = new Buffer(length); 
 offset = 0; 
 //设置对应的值,新建buffer对象 
 
 //把callback做一个代理,根据传入的编码方式,把结果按照指定的编码,传入回调函数 
 callback = function(err, bytesRead) { 
  if (!cb) return; 
  //如果回调函数不存在,则直接退出 
  
  var str = (bytesRead > 0) ? buffer.toString(encoding, 0, bytesRead) : ''; 
 
  //注意,当读取文件成功后,执行了wrapper的回调,从wrapper中, 
  //执行到该callback回调时,并没有传入buffer对象, 
  //并且,调用read中的回调的三个参数是:err,str(按照指定编码之后的字符串), 
  //bytesRead(读取字节数),并没有buffer对象传入 
  (cb)(err, str, bytesRead); 
 }; 
 } 
 
 function wrapper(err, bytesRead) { 
 // Retain a reference to buffer so that it can't be GC'ed too soon. 
 // 由这里可以看出,在C++读取文件时,回调函数只有两个值 
 //err对象和真实读取的字节数,至于buffer对象,则是nodejs代理之后 
 //给添加上的 
 callback && callback(err, bytesRead || 0, buffer); 
 } 
 
 //创建一个实例,定义oncomplete属性 
 //该实例,按照猜测,应该是分段读取文件的一个对象 
 //当读取文件完成之后,会执行oncomplete方法 
 var req = new FSReqWrap(); 
 req.oncomplete = wrapper; 
 
 //调用C++的接口,开始读取文件 
 binding.read(fd, buffer, offset, length, position, req); 
};

看了上面的源码分析,那么也就发现了另外一种使用read的方法了,即,不输入buffer和offset,添加encoding的5个参数的使用,举一个最简单的实例吧。

var fs = require("fs"); 
 
fs.open("fs.txt","r",function(err,fd){ 
 //读取fs.text,文件的内容为“123456789”,长度为9 
 var buf1 = new Buffer([0,0,0,0,0,0,0,0,0,0]); 
  
 fs.read(fd,6,4,null,function(err,str,bytesRead){ 
  console.log(err); 
  //null 
  console.log("str="+str); 
  //str=56789 
  console.log("bytesRead="+bytesRead); 
  //bytesRead=5 
 }); 
  
});

注意,当不传入buffer对象时,回调函数中的三个参数也相应的有了变化,详情请看前面的实例代码中,回调函数的参数以及源码中的注释。

继续看下readSync的源码,在本文的前面,也给出了一个readSync的示例,当传入buffer对象时,返回值是读取到真是字节数,那么,既然read方法可以省略buffer对象,改为返回读取到的字符串,那么readSync方法呢?这个就让我们看下源码中,是如何处理这些数据的。

fs.readSync = function(fd, buffer, offset, length, position) { 
 var legacy = false; 
 if (!util.isBuffer(buffer)) { 
 // legacy string interface (fd, length, position, encoding, callback) 
 //该部分的处理,和read方法内部,完全相同,不再注释。 
 //唯一区别,legacy标识符,标志是否传入了buffer,为false时,表示传入了 
 legacy = true; 
 var encoding = arguments[3]; 
 
 assertEncoding(encoding); 
 
 position = arguments[2]; 
 length = arguments[1]; 
 buffer = new Buffer(length); 
 
 offset = 0; 
 } 
 
 //C++的read方法,如果传入了第六个参数,则属于读取成功之后,执行的回调相关的对象 
 //如果不传入,则返回值为读取到的真是字节数,该数小于等于length 
 var r = binding.read(fd, buffer, offset, length, position); 
 if (!legacy) { 
 //如果,传入了buffer对象,则直接返回读取到的真是字节数 
 return r; 
 } 
 
 var str = (r > 0) ? buffer.toString(encoding, 0, r) : ''; 
 //如果没有传入buffer对象,那么返回一个数组,该数组包含两个元素, 
 //字符串和读取到的字节数 
 return [str, r]; 
};

那么接下来看下,如果不传入buffer对象时的一个示例吧:

var fs = require("fs"); 
 
fs.open("fs.txt","r",function(err,fd){ 
 //读取fs.text,文件的内容为“123456789”,长度为9 
 var buf1 = new Buffer([0,0,0,0,0,0,0,0,0,0]); 
  
 var arr = fs.readSync(fd,6,4,null); 
 console.log(arr); 
 //["56789",5] 
});

OK,到这里,关于read和readSync方法的使用及一些原理性东西,也基本说完了。

2:write和writeSync方法

有读取的方法,那么就必然有写入的方法了,要么flag=w不就无用了么。并且看到了前面的关于read的一些使用,那么接下来,对于write的使用,看起来就变得更加的简单了,现在直接看下示例:

var fs = require("fs"); 
 
fs.open("fs.txt","a+",function(err,fd){ 
 //读取fs.text,文件的内容为“123456789”,长度为9 
 var buf1 = new Buffer("我喜爱Nodejs"); 
 console.log(buf1); 
 //显示buf1的buffer数据 
 //计算buf1的长度,把该数据全部写入到fs.txt文件中 
 fs.write(fd,buf1,0,buf1.length,0,function(err,len,buf){ 
  console.log("len="+len); 
  //写入的长度 
   
  //写入的buf,其实和buf1完全相等 
  console.log(buf); 
  fs.read(fd,len,9,"utf8",function(err,str,len2){ 
   console.log("len2="+len2); 
   //读取从9开始的数据 
   console.log("str="+str); 
   //读取相应得到的字符串 
   //我喜爱Nodejs 
  }); 
 }); 
});

从上面这个示例可以看出,write方法和read方法,使用基本是完全一样的,只是一个是在读取文件一个是在写入文件,前提也是需要你在open打开文件时,使用的flag打开文件方式,要支持读写才行。

既然,write和read是相同的使用方法,那么也可以不定义buffer的直接写入数据,所以,可以继续看下面的这个示例:

var fs = require("fs"); 
 
fs.open("fs.txt","a+",function(err,fd){ 
 //读取fs.text,文件的内容为“123456789”,长度为9 
  
 //复杂的写法,和简单的写法,就看个人喜好了,0代表的是字符串的开始位置 
 //fs.write(fd,"我喜爱Nodejs",0,"utf8",function(err,len,str) 
 fs.write(fd,"我喜爱Nodejs",function(err,len,str){ 
  console.log("len="+len); //len=15 
  //写入的长度 
   
  //当直接写入字符串时,返回的也不再是buffer对象,而是字符串 
  console.log("str="+str); //我喜爱Nodejs 
  fs.read(fd,len,9,"utf8",function(err,str,len2){ 
   console.log("len2="+len2); //len2=15 
   //读取从9开始的数据 
   console.log("str="+str); 
   //读取相应得到的字符串 
   //我喜爱Nodejs 
  }); 
 }); 
});

这里就不再分析源码了,基本上write的源码和read的源码处理方式类似,只是在最后调用C++接口不同而已,所以这里也就不再占用空间了。有兴趣的可以直接去nodejs的github源码中,查看:fs.js。

关于writeSync的用法,用法和write是相同的,只是不需要回调函数,并且也不需要返回写入的数据,所以,和readSync的区别,也就是,readSync在不传入buffer时,会返回一个长度为2的数组,而writeSync不受buffer对象的影响,只要写入成功,就会返回写入的真实字节数。
不加示例,不加源码分析,请参考上面的read方法,readSync方法和write方法,也可以参考nodejs的API文档:Nodejs的API中文版。

总结

本篇的read和write是文档操作的基础,是属于最基本的操作,也是最重要的操作,本篇也是属于fs模块中的基本使用方法,对于以后学习其他方法,以及更好的了解fs模块有重要的作用,好好学习,天天向上。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
NodeJS学习笔记之网络编程
Aug 03 NodeJs
nodejs事件的监听与触发的理解分析
Feb 12 NodeJs
Nodejs express框架一个工程中同时使用ejs模版和jade模版
Dec 28 NodeJs
NodeJS创建基础应用并应用模板引擎
Apr 12 NodeJs
nodejs动态创建二维码的方法
Aug 12 NodeJs
详解nodeJs文件系统(fs)与流(stream)
Jan 24 NodeJs
nodejs实现的连接MySQL数据库功能示例
Jan 25 NodeJs
nodejs中密码加密处理操作详解
Mar 20 NodeJs
nodejs express配置自签名https服务器的方法
May 22 NodeJs
基于nodejs的微信JS-SDK简单应用实现
May 21 NodeJs
Nodejs使用archiver-zip-encrypted库加密压缩文件时报错(解决方案)
Nov 18 NodeJs
Nodejs 数组的队列以及forEach的应用详解
Feb 25 NodeJs
学习 NodeJS 第八天:Socket 通讯实例
Dec 21 #NodeJs
详解Nodejs基于mongoose模块的增删改查的操作
Dec 21 #NodeJs
nodejs redis 发布订阅机制封装实现方法及实例代码
Dec 15 #NodeJs
解析NodeJs的调试方法
Dec 11 #NodeJs
nodejs连接mongodb数据库实现增删改查
Dec 01 #NodeJs
Nodejs 搭建简单的Web服务器详解及实例
Nov 30 #NodeJs
Nodejs下用submit提交表单提示cannot post错误的解决方法
Nov 21 #NodeJs
You might like
如何跨站抓取别的站点的页面的补充
2006/10/09 PHP
PHP Session变量不能传送到下一页的解决方法
2009/11/27 PHP
利用PHP访问MySql数据库的逻辑操作以及增删改查的实例讲解
2017/08/30 PHP
PHP+redis实现微博的推模型案例分析
2019/07/10 PHP
jquery子元素过滤选择器使用示例
2013/06/24 Javascript
JS中的this变量的使用介绍
2013/10/21 Javascript
原生JS操作网页给p元素添加onclick事件及表格隔行变色
2013/12/01 Javascript
JS+CSS实现淡入式焦点图片幻灯切换效果的方法
2015/02/26 Javascript
使用JSON作为函数的参数的优缺点
2016/10/27 Javascript
JS正则匹配中文的方法示例
2017/01/06 Javascript
Js apply方法详解
2017/02/16 Javascript
解决给dom元素绑定click等事件无效问题的方法
2017/02/17 Javascript
nodejs socket实现的服务端和客户端功能示例
2017/06/02 NodeJs
angular ng-click防止重复提交实例
2017/06/16 Javascript
vue富文本编辑器组件vue-quill-edit使用教程
2018/09/21 Javascript
react配置antd按需加载的使用
2019/02/11 Javascript
react实现同页面三级跳转路由布局
2019/09/26 Javascript
详解vue中使用axios对同一个接口连续请求导致返回数据混乱的问题
2019/11/06 Javascript
[46:47]2014 DOTA2国际邀请赛中国区预选赛5.21 LGD-CDEC VS NE
2014/05/22 DOTA
[55:32]2018DOTA2亚洲邀请赛 4.4 淘汰赛 EG vs LGD 第二场
2018/04/05 DOTA
[01:15:00]LGD vs Mineski Supermajor 胜者组 BO3 第一场 6.5
2018/06/06 DOTA
初学python数组的处理代码
2011/01/04 Python
python实现mysql的单引号字符串过滤方法
2015/11/14 Python
Python实现定时任务
2017/02/08 Python
Python文件打开方式实例详解【a、a+、r+、w+区别】
2019/03/30 Python
python判断单向链表是否包括环,若包含则计算环入口的节点实例分析
2019/10/23 Python
海外淘书首选:AbeBooks
2017/07/31 全球购物
法国包包和行李箱销售网站:Bagage24.fr
2020/03/24 全球购物
一份全面的PHP面试问题考卷
2012/07/15 面试题
PHP面试题及答案一
2012/06/18 面试题
应聘医药代表职位求职信
2013/10/21 职场文书
全国优秀辅导员事迹材料
2014/05/14 职场文书
前台接待员岗位职责
2015/04/15 职场文书
Nginx实现高可用集群构建(Keepalived+Haproxy+Nginx)
2021/05/27 Servers
Nginx配置文件详解以及优化建议指南
2021/09/15 Servers
python 管理系统实现mysql交互的示例代码
2021/12/06 Python