Node.js本地文件操作之文件拷贝与目录遍历的方法


Posted in Javascript onFebruary 16, 2016

文件拷贝
NodeJS 提供了基本的文件操作 API,但是像文件拷贝这种高级功能就没有提供,因此我们先拿文件拷贝程序练手。与 copy 命令类似,我们的程序需要能接受源文件路径与目标文件路径两个参数。

小文件拷贝
我们使用 NodeJS 内置的 fs 模块简单实现这个程序如下。

var fs = require('fs');

function copy(src, dst) {
  fs.writeFileSync(dst, fs.readFileSync(src));
}

function main(argv) {
  copy(argv[0], argv[1]);
}

main(process.argv.slice(2));

以上程序使用 fs.readFileSync 从源路径读取文件内容,并使用 fs.writeFileSync 将文件内容写入目标路径。

豆知识: process 是一个全局变量,可通过 process.argv 获得命令行参数。由于 argv[0] 固定等于 NodeJS 执行程序的绝对路径,argv[1] 固定等于主模块的绝对路径,因此第一个命令行参数从 argv[2] 这个位置开始。

大文件拷贝
上边的程序拷贝一些小文件没啥问题,但这种一次性把所有文件内容都读取到内存中后再一次性写入磁盘的方式不适合拷贝大文件,内存会爆仓。对于大文件,我们只能读一点写一点,直到完成拷贝。因此上边的程序需要改造如下。

var fs = require('fs');

function copy(src, dst) {
  fs.createReadStream(src).pipe(fs.createWriteStream(dst));
}

function main(argv) {
  copy(argv[0], argv[1]);
}

main(process.argv.slice(2));

以上程序使用 fs.createReadStream 创建了一个源文件的只读数据流,并使用 fs.createWriteStream 创建了一个目标文件的只写数据流,并且用 pipe 方法把两个数据流连接了起来。连接起来后发生的事情,说得抽象点的话,水顺着水管从一个桶流到了另一个桶。

遍历目录

遍历目录是操作文件时的一个常见需求。比如写一个程序,需要找到并处理指定目录下的所有JS文件时,就需要遍历整个目录。

递归算法
遍历目录时一般使用递归算法,否则就难以编写出简洁的代码。递归算法与数学归纳法类似,通过不断缩小问题的规模来解决问题。以下示例说明了这种方法。

function factorial(n) {
  if (n === 1) {
    return 1;
  } else {
    return n * factorial(n - 1);
  }
}

上边的函数用于计算 N 的阶乘(N!)。可以看到,当 N 大于 1 时,问题简化为计算 N 乘以 N-1 的阶乘。当 N 等于 1 时,问题达到最小规模,不需要再简化,因此直接返回 1。

陷阱: 使用递归算法编写的代码虽然简洁,但由于每递归一次就产生一次函数调用,在需要优先考虑性能时,需要把递归算法转换为循环算法,以减少函数调用次数。

遍历算法
目录是一个树状结构,在遍历时一般使用深度优先+先序遍历算法。深度优先,意味着到达一个节点后,首先接着遍历子节点而不是邻居节点。先序遍历,意味着首次到达了某节点就算遍历完成,而不是最后一次返回某节点才算数。因此使用这种遍历方式时,下边这棵树的遍历顺序是 A > B > D > E > C > F。

A
     / \
    B  C
    / \  \
   D  E  F

同步遍历
了解了必要的算法后,我们可以简单地实现以下目录遍历函数。

function travel(dir, callback) {
  fs.readdirSync(dir).forEach(function (file) {
    var pathname = path.join(dir, file);

    if (fs.statSync(pathname).isDirectory()) {
      travel(pathname, callback);
    } else {
      callback(pathname);
    }
  });
}

可以看到,该函数以某个目录作为遍历的起点。遇到一个子目录时,就先接着遍历子目录。遇到一个文件时,就把文件的绝对路径传给回调函数。回调函数拿到文件路径后,就可以做各种判断和处理。因此假设有以下目录:

- /home/user/
  - foo/
    x.js
  - bar/
    y.js
  z.css

使用以下代码遍历该目录时,得到的输入如下。

travel('/home/user', function (pathname) {
  console.log(pathname);
});
/home/user/foo/x.js
/home/user/bar/y.js
/home/user/z.css

异步遍历
如果读取目录或读取文件状态时使用的是异步API,目录遍历函数实现起来会有些复杂,但原理完全相同。travel函数的异步版本如下。

function travel(dir, callback, finish) {
  fs.readdir(dir, function (err, files) {
    (function next(i) {
      if (i < files.length) {
        var pathname = path.join(dir, files[i]);

        fs.stat(pathname, function (err, stats) {
          if (stats.isDirectory()) {
            travel(pathname, callback, function () {
              next(i + 1);
            });
          } else {
            callback(pathname, function () {
              next(i + 1);
            });
          }
        });
      } else {
        finish && finish();
      }
    }(0));
  });
}

这里不详细介绍异步遍历函数的编写技巧,在后续章节中会详细介绍这个。总之我们可以看到异步编程还是蛮复杂的。

Javascript 相关文章推荐
取键盘键位ASCII码的网页
Jul 30 Javascript
语义化 H1 标签
Jan 14 Javascript
深入理解JavaScript系列(1) 编写高质量JavaScript代码的基本要点
Jan 15 Javascript
window.location.href的用法(动态输出跳转)
Aug 09 Javascript
js实现上传图片预览的方法
Feb 09 Javascript
浅谈JavaScript的push(),pop(),concat()方法
Jun 03 Javascript
微信公众号支付H5调用支付解析
Nov 04 Javascript
vue自定v-model实现表单数据双向绑定问题
Sep 03 Javascript
JavaScript常见事件对象与操作实例总结
Jan 05 Javascript
详解vue引入子组件方法
Feb 12 Javascript
layui实现鼠标移动到单元格上显示数据的方法
Sep 11 Javascript
vue2.0实现列表数据增加和删除
Jun 17 Javascript
详解Node.js包的工程目录与NPM包管理器的使用
Feb 16 #Javascript
javascript每日必学之运算符
Feb 16 #Javascript
解析Node.js基于模块和包的代码部署方式
Feb 16 #Javascript
javascript每日必学之基础入门
Feb 16 #Javascript
快速掌握Node.js环境的安装与运行方法
Feb 16 #Javascript
js实现异步循环实现代码
Feb 16 #Javascript
JavaScript实现跑马灯抽奖活动实例代码解析与优化(二)
Feb 16 #Javascript
You might like
php编写一个简单的路由类
2011/04/13 PHP
PHP中文字符串截断无乱码解决方法
2016/10/10 PHP
PHP crypt()函数的用法讲解
2019/02/15 PHP
判断是否输入完毕再激活提交按钮
2006/06/26 Javascript
我也种棵OO树JXTree[js+css+xml]
2007/04/02 Javascript
DOM_window对象属性之--clipboardData对象操作代码
2011/02/03 Javascript
基于jQuery+HttpHandler实现图片裁剪效果代码(适用于论坛, SNS)
2011/09/02 Javascript
用js实现trim()的解决办法
2013/04/16 Javascript
不用锚点也可以平滑滚动到页面的指定位置实现代码
2013/05/08 Javascript
JS自定义功能函数实现动态添加网址参数修改网址参数值
2013/08/02 Javascript
JavaScript运行时库属性一览表
2014/03/14 Javascript
javascript获取四位数字或者字母的随机数
2015/01/09 Javascript
JavaScript中Number.NEGATIVE_INFINITY值的使用详解
2015/06/05 Javascript
一不小心就做错的JS闭包面试题
2015/11/25 Javascript
详解Node.js包的工程目录与NPM包管理器的使用
2016/02/16 Javascript
Jquery Easyui选项卡组件Tab使用详解(10)
2016/12/18 Javascript
HTML5 js实现拖拉上传文件功能
2020/11/20 Javascript
原生js封装自定义滚动条
2017/03/24 Javascript
vue 使用自定义指令实现表单校验的方法
2018/08/28 Javascript
详解如何使用nvm管理Node.js多版本
2019/05/06 Javascript
vue穿梭框实现上下移动
2021/01/29 Vue.js
详解Python中的循环语句的用法
2015/04/09 Python
解析Python中的异常处理
2015/04/28 Python
讲解Python中if语句的嵌套用法
2015/05/14 Python
python清理子进程机制剖析
2017/11/23 Python
Python3利用openpyxl读写Excel文件的方法实例
2021/02/03 Python
游戏商店:Eneba
2020/04/25 全球购物
C语言编程题
2015/03/09 面试题
回门宴答谢词
2014/01/13 职场文书
食品行业求职人的自我评价
2014/01/19 职场文书
教师校本培训方案
2014/02/26 职场文书
管理岗位竞聘演讲稿
2014/08/18 职场文书
党员思想汇报材料
2014/12/19 职场文书
休学证明范本
2015/06/19 职场文书
清明节主题班会
2015/08/14 职场文书
HTML5 新增内容和 API详解
2021/11/17 HTML / CSS