NodeJS -- 文件操作

Buffer(数据块)

JS语言自身只有字符串数据类型,没有二进制数据类型,因此NodeJS提供了一个与String对等的全局构造函数Buffer来提供对二进制数据的操作。除了可以读取文件得到Buffer的实例外,还能够直接构造,例如:

var bin = new Buffer([0x68, 0x65, 0x6c, 0x6c, 0x6f]);

Buffer与字符串类似,除了可以用.length属性得到字节长度外,还可以使用[index]方式读取指定位置的字节,例:

bin[0]; // => 0x68;

Buffer与字符串能够互相转化,例:

var str = bin.toString('utf-8'); // => 'hello'
var bin = new Buffer('hello','utf-8'); // =><Buffer 68 65 6c 6c 6f>

Buffer与字符串一个重要区别:字符串是只读的,并且对字符串的任何修改得到的都是一个新字符串,原字符串保持不变。至于Buffer,更像是可以做指针操作的C语言数组。例可以使用[index]方式直接修改某个位置的字节:

bin[0] = 0x48;

而.slice方法也不是返回一个新的Buffer,而更像是反回了指向原Buffer中间某个位置的指针:

因此对.slice方法返回的Buffer的修改会作用于原Buffer。

为此若要拷贝一份Buffer,需要先创建一个新的Buffer,通过.copy把原Buffer的数据复制过去,类似于先申请新内存,再把已有内存的数据复制过去。
总之,Buffer将JS数据处理能力扩展到了任意二进制数据

Stream(数据流)

当内存中无法一次装下需要处理的数据时,或者一边读取一遍处理更加高效时,我们就需要用到数据流。NodeJS通过各种Stream来提供对数据流的操作,在大文件拷贝时,可以为数据来源创建一个只读数据流,例:

var rs = fs.createReadStream(pathName);

rs.on('data', function(chunk) {
doSomething(chunk);
}); rs.on('end', function() {
cleanUp();
});

注:Stream基于事件机制工作,所有Stream实例都继承于NodeJS提供的EventEmitter

上边的代码中data事件会源源不断地被触发,不管doSomething()函数是否处理得过来。代码可以继续作如下改造,以解决这个问题:

var rs = fs.createReadStream(src);

rs.on('data', function(chunk) {
rs.pause();
doSomething(chunk, function() {
rs.resume();
});
}); rs.on('end', function() {
cleanUp();
});
以上给doSomething()函数加上了回调函数,因此我们可以在处理数据前暂停数据读取,并在处理数据后继续读取数据。
此外,还可以为数据目标创建一个只写数据流,例:

var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst); rs.on('data', function(chunk) {
ws.write(chunk);
}); rs.on('end', function(chunk) {
ws.end():
});

以上代码看起来就像是一个文件拷贝程序了,不过,依然存在写入速度跟不上读取速度,会导致缓存爆仓的问题,我们可以根据.write方法的返回值来判断传入的数据是写入目标了,还是临时放在了缓存了,并根据drain事件来判断什么时候只写数据流已经将缓存中的数据写入目标,可以传入下一个待写数据了:

var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst);
rs.on('data', function(chunk) {
if(ws.write(chunk) === false) {
rs.pause();
}
}); rs.on('end', function() {
ws.end();
}); rs.on('drain', function() {
rs.resume();
});

实现了数据从只读数据流到只写数据流的搬运,并包括了放爆仓控制,NodeJS直接提供了.pipe方法来做这件事,内部实现方式与上述代码类似

File System(文件系统)

NodeJS通过内置fs模块提供对文件的操作,fs模块提供的API基本上可以分为以下三类:
文件属性读写:
常用的有:fs.stat、fs.chmod、fs.chown等等
文件内容读写:
常用的有:fs.readFile、fs.readdir、fs.writeFile、fs.mkdir等等
底层文件操作:
常用的有:fs.open、fs.read、fs.write、fs.close等等
NodeJS最精华的异步IO模型在fs模块里有着充分的体现,例如上面提到的API都通过回调函数传递结果,例:
fs.readFile(pathName, function(err, data) {
if(err) {
// Deal with error.
} else {
// Deal with data
}
});
基本上所有fs模块API回调参数都有两个,第一个参数在有错误发生时等于异常对象,第二个参数始终用于返回API方法执行结果。
此外,fs模块的所有异步API都有对应的同步版本,用于无法使用异步操作时,或同步操作更方便时。同步API除了方法名末尾多了一个Sync之外,异常对象的处理需要使用异常捕获

Path(路径)

操作文件时难免与文件路径打交道,NodeJS提供了path内置模块来简化路径相关操作,并提升代码可读性。

path.normalize

将传入的路径转换为标准路径,具体讲的话,除了解析路径中的.与..之外,还能去掉多余的斜杠,如果程序需要使用路径作为某些数据的索引,但有允许用户随意输入路径时,就需要使用该方法保证路径的唯一性,例:

var cache = {};
function store(key, value) {
cache[path.normalize(key)] = value;
}
store('foo/bar', 1);
store('foo//baz//../bar', 2);
console.log(cache); // {'foo/bar': 2}

注意:标准化之后的路径里的斜杠在Window系统下是\,而在linux系统下是/,若想保证任何系统都使用/作为路径分隔符的话,需要.replace(/\\/g, '/')再替换下标准路径

path.join

将传入的多个路径拼接为标准路径,该方法可避免手工拼接字符串的繁琐,并且能在不同系统下正确使用相应路径分隔符。例:

path.join('foo/', 'baz/', '../bar'); // => "foo/bar"

path.extname

可获取文件的扩展名,当我们需要根据不同文件扩展名做不同操作时,该方法就显得很好用,例:

path.extname('foo/bar.js'); // => ".js"

遍历目录

遍历目录是操作文件时一个常见需求,例,写一个程序用于查找指定目录下的所有js文件时,就需要遍历整个目录
递归算法
遍历目录一般使用递归算法,否则就较难写出简洁的代码
注:使用递归算法虽然简洁,但每次递归都会产生函数调用,在需要优先考虑性能时,需要把递归算法转化为循环算法,以减少函数调用次数
遍历算法
目录是一个树状结构,遍历时一般采用深度优先+先序遍历算法
同步遍历
结合算法,使用文件系统的同步API
异步遍历
结合算法,使用文件系统的异步API,实现会变得复杂,但原理相同

文本编码

常用的文本编码有UTF8和GBK两种,并且UTF8还可能带有BOM,在读取不同编码的文本文件时,需要将文件内容转换为JS使用的UTF8编码字符串后才能正常处理

BOM的移除

BOM用于标记一个文件使用Unicode编码,其本身是一个Unicode字符(“\uFEFF”),位于文本文件头部,在不同Unicode编码下,BOM字符对应的二进制字节如下:

因此我们可以根据文本文件头几个字节等于啥来判断文件是否包含BOM,以及使用哪种Unicode编码,但BOM并不属于文件的一部分,需要去掉,否则在某些应用场景下会有问题。以下代码实现了识别和去除UTF8 BOM的功能

function readText(pathName) {
var bin = fs.readFileSync(pathName);
if(bin[0] === 0xEF && bin[1] === 0xBB && bin[2] === 0xBF) {
bin = bin.slice(3);
}
return bin.toString('utf-8');
}
注:
1、如果不是很在意性能,可以使用fs模块的同步API,更容易理解
2、需要对文件读写做到字节级别的精细控制时,请使用fs模块文件底层操作API
3、不要使用拼接字符串的方式来处理路径,使用path模块

NodeJS学习之文件操作的更多相关文章

  1. HTML5学习之文件操作(九)

    之前我们操作本地文件都是使用flash.silverlight或者第三方的activeX插件等技术,由于使用了这些技术后就很进行跨平台的处理,另外就是让我们的web应用依赖了第三方的插件,而不是很独立 ...

  2. python学习笔记:文件操作和集合(转)

    转自:http://www.nnzhp.cn/article/16/ 这篇博客来说一下python对文件的操作. 对文件的操作分三步: 1.打开文件获取文件的句柄,句柄就理解为这个文件 2.通过文件句 ...

  3. python学习总结---文件操作

    # 文件操作 ### 目录管理(os) - 示例 ```python # 执行系统命令 # 清屏 # os.system('cls') # 调出计算器 # os.system('calc') # 查看 ...

  4. erlang学习笔记(文件操作)

    参考这里和这里了解到的文件操作的模块有很多:kernel下有:file,stdlib下有:filelib,filename,file_sorter.(具体查看官方文档)

  5. Smart210学习记录-------文件操作

    一.linux文件操作(只能在linux系统上用) 创建:int creat(const char* filename, mode_t mode) filename 表示要创建的文件名,mode表示对 ...

  6. python学习day8 文件操作(深度学习)

    文件操作 (day7内容扩展) 1 文件基本操作 obj = open('路径',mode='模式',encoding='编码')obj.write()obj.read()obj.close() 2 ...

  7. python学习日记(文件操作)

    文件操作概述 计算机系统分为:操作系统,计算机硬件,应用程序. 我们用python或其他语言编写的应用程序若想要把数据永久保存下来,必须要保存于硬盘中,这就涉及到应用程序要操作硬件,众所周知,应用程序 ...

  8. nodejs笔记之文件操作

    文件操作包含: 读取文件 写入文件 删除文件 创建目录 删除目录 读取文件: // 异步操作 var fs = require("fs"); fs.readFile(". ...

  9. python 学习分享-文件操作篇

    文件操作 f_open=open('*.txt','r')#以只读的方式(r)打开*.txt文件(需要与py文件在同一目录下,如果不同目录,需写全路径) f_open.close()#关闭文件 打开文 ...

随机推荐

  1. javascript自执行函数为什么要把windows作为参数传进去

    http://segmentfault.com/q/1010000000311686 (function (window, $, undefined) { play=function(){ $(&qu ...

  2. svn 冲突解决

    svn: E155015: Aborting commit: '$path + $file' remains in conflict 解决步骤 1.svn resolved 'file'执行后结果Re ...

  3. css去掉a标签点击后的虚线框(转自网络)

    outline是css3的一个属性,用的很少. 声明,这是个不能兼容的css属性,在ie6.ie7.遨游浏览器都不兼容. outline控制的到底是什么呢? 当聚焦a标签的时候,在a标签的区域周围会有 ...

  4. 从Windows 服务器通过sync向Linux服务器定时同步文件

    本文解决的是Windows 下目录及文件向Linux同步的问题,Windows向 Windows同步的请参考:http://www.idcfree.com/article-852-1.html 环境介 ...

  5. SMARTFORM报表程序设计(1)

    SMARTFORM是SAP提供的一款商务单据及报表设置工具,可以在FORM中实现数据的计算及转换等功能,并能在FORM创建的同时生成功能模块,为FORM和ABAP程序提供更为强大的参数接口.输入T-C ...

  6. C#调用C++ dll时,结构体引用传参的方法

    写了一个C++的LogLog Logit 四参数等算法的接口dll,给C#调用,但是发现传参有问题 如 extern "C" _declspec(dllexport)  bool ...

  7. 微信公共服务平台开发(.Net 的实现)13-------网页授权(下 :C#代码的实现 )

    接着上次的理论,我们这次来研究用代码实现“网页授权获取用户基本信息”,首先我们需要一个链接指向微信的授权页面,在微信开发平台中已经说了,这个链接必须在微信客户端中打开,那么我们就干脆使用文本消息来完成 ...

  8. iOS开发——实战篇Swift篇&UItableView结合网络请求,多线程,数据解析,MVC实战

    UItableView结合网络请求,多线程,数据解析,MVC实战 学了这么久的swift都没有做过什么东西,今天就以自己的一个小小的联系,讲一下,怎么使用swift在实战中应用MVC,并且结合后面的高 ...

  9. Java虚拟机工作原理具体解释

    一.类载入器 首先来看一下java程序的运行过程. 从这个框图非常easy大体上了解java程序工作原理.首先,你写好java代码,保存到硬盘其中.然后你在命令行中输入 javac YourClass ...

  10. 算法设计 - LCS 最长公共子序列&&最长公共子串 &&LIS 最长递增子序列

    出处 http://segmentfault.com/blog/exploring/ 本章讲解:1. LCS(最长公共子序列)O(n^2)的时间复杂度,O(n^2)的空间复杂度:2. 与之类似但不同的 ...