文件系统:通过异步和同步的方法处理文件
fs模块概述
-
fs方法(支持的POSIX文件方法):
- fs.rename:改变文件名称
- fs.truncate:截断或者扩展文件到指定的长度
- fs.ftruncate:和truncate一样,但将文件描述符作为参数
- fs.chown:改变文件的所有者以及组
- fs.fchown:和chown一样,但不将文件描述符作为参数
- fs.lchown:和chown一样,但不解析符号链接
- fs.chmod:修改文件权限
- fs.fchmod:和chomd一样,但将文件描述符作为参数
- fs.lchomd:和chomd一样,但不解析符号链接
- fs.state:获取文件状态
- fs.lstat:和stat一样,但返回信息是关于符号链接而不是它指向的内容
- fs.fstat:和stat一样,但将文件描述符作为参数
- fs.link:创建一个硬链接
- fs.symlink:创建一个软链接
- fs.realpath:返回规范的绝对路径名
- fs.readlink:读取一个软链接的值
- fs.unlink:删除文件
- fs.rmdir:删除文件目录
- fs.mkdir:创建文件目录
- fs.readdir:读取一个文件目录的内容
- fs.close:关闭一个文件描述符
- fs.open:打开或者创建一个文件用来读取或者写入
- fs.utimes:设置文件的读取和修改时间
- fs.futimes:和utimes一样,但将文件描述符作为参数
- fs.fsync:同步磁盘中的文件数据
- fs.write:写入数据到一个文件
- fs.read:读取一个文件的数据
- <font color=grey>文件描述符:文件的索引
- 符号链接:又叫软链接,是一类特殊的文件,这个文件包含了另一个文件的路径名(绝对路径或者相对路径)</font>
-
fs操作策略:
- 流:通过fs.createReadStream和fs.createWriteStream以及pipe连接进行流操作
- 批量文件操作:通过fs.readFile等方法进行文件的一次性加载到内存或者一次性写入文件,操作后保存在一个buffer对象当中
-
文件监视:
- fs.watch:
- 特点:一个更可靠的实现使得文件改变的事件能够总是被执行,一个更快的实现,当事件发生时能够立即通知到Node进程。
- 监测文件修改,这个方法是通过监听操作系统提供的各种“事件”(内核发布的消息)实现的。(在Mac上行为不正常)
- fs.watchFile:
- 在监听一个目录时,许多对文件的更新不会被fs.watchFile监听到。如果你想使用fs.watchFile,那么可以监听单个文件
- 返回的值中会包含两个fs.Stats对象来表示文件当前和过去的状态
- 轮询监测(时间参数可调,但是如果监测的文件过多的话内存吃不消)
- fs.watch:
-
同步的替代方案:
- 一般在异步方法的后面添加Sync即为该方法的同步方法
- 同步方法会阻塞你的单线程Node进程直到它结束,所以一般而言同步方法应该在第一次初始化的时候使用,而不能再回调中使用。
-
递归文件
-
同步递归:
- 因为是同步方法,可以在处理函数外面用try,catch来获取捕捉异常,非常的方便。
-
异步递归:
- 使用计数器来统计所以异步方法是否全部完成。在异步方法开始之前累加计数器,在回调开始之前减去计数器。
- 使用错误处理句柄来处理异常,用来确保如果有多个错误,但是回调只会执行一次。
var errored = false; function error(err) { if (!errored) cb(err); errored = true; }
-
性能分析:
- 同步的方法比较快,因为同步的方法不会延迟执行,就算相对的异步方法执行很快。当CPU准备妥当时,同步方法会立即执行,会保证你只需要等待必要的I/O完成。但是同步方法在等待运行的期间会阻塞其他事情的发生。
-
-
文件锁
- node进行文件咨询锁的思想是创建利用锁文件:
-
使用独占标记创建锁文件
- fs模块为所有需要打开文件的方法提供了一个x标记。这告诉操作系统这个文件应该以独占模式打开(O_EXCL)。当使用这个方法时,若这个文件存在,文件不能被打开。
fs.open('config.lock', 'wx', function(err)) { if (err) return console.err(err); })
-
使用mkdir创建锁文件
- 当锁文件在网络磁盘上时独占模式可能不能正常工作,因为一些系统在网络磁盘上并不识别O_EXCL标记。当目录已经存在时mkdir方法会失败。
-
- node进行文件咨询锁的思想是创建利用锁文件:
-
文件数据库
- 策略是不断的追加记录,在处理的时候不断的覆盖原始记录。
var fs = require('fs') var EventEmitter = require('events').EventEmitter var Database = function(path) { this.path = path // 数据库key/value映射表 this._records = Object.create(null); this._writeStream = fs.createWriteStream(this.path, { encoding: 'utf8', flags: 'a' }) this._load() } // 继承自EventEmitter,让它有可以监听事件、传递事件的功能 Database.prototype = Object.create(EventEmitter.prototype) Database.prototype._load = function() { var stream = fs.createReadStream(this.path, { encoding: 'utf8' }); var database = this; var data = ''; // 当输入流准备好时触发 stream.on('readable', function() { data += stream.read(); // 每一行是一组数据 var records = data.split('\n'); // 这里存在一点疑问,书上的解释是获取最后一个可能未完成的记录(但是不是会把它从数组中删除吗?) data = records.pop(); for (var i = 0; i < records.length; i++) { try { var record = JSON.parse(records[i]); if (record.value == null) { delete database._records[record.key]; } else { database._records[record.key] = record.value; } } catch(e) { database.emit('error', 'found invalid record:', records[i]); } } }); // 流处理完后发送load事件给消费者 stream.on('end', function() { database.emit('load'); }) }
网友评论