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 相关文章推荐
如何用javascript去掉字符串里的所有空格
Feb 08 Javascript
firefox和IE系列的相关区别整理 以备后用
Dec 28 Javascript
js判断横竖屏及禁止浏览器滑动条示例
Apr 29 Javascript
javascript 动态创建表格
Jan 08 Javascript
ECMAScript6中Set/WeakSet详解
Jun 12 Javascript
chrome下判断点击input上标签还是其余标签的实现方法
Sep 18 Javascript
jquery实现输入框实时输入触发事件代码
Dec 21 Javascript
基于JavaScript 性能优化技巧心得(分享)
Dec 11 Javascript
浅谈VUE防抖与节流的最佳解决方案(函数式组件)
May 22 Javascript
vue视频播放暂停代码
Nov 08 Javascript
vuex存储token示例
Nov 11 Javascript
Vue实现兄弟组件间的联动效果
Jan 21 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更改目录及子目录下所有的文件后缀的代码
2010/09/24 PHP
php入门学习知识点二 PHP简单的分页过程与原理
2011/07/14 PHP
php四种基础算法代码实例
2013/10/29 PHP
PDO防注入原理分析以及注意事项
2015/02/25 PHP
PHP实现仿百度文库,豆丁在线文档效果(word,excel,ppt转flash)
2016/03/10 PHP
PHP+mysql实现的三级联动菜单功能示例
2019/02/15 PHP
php弹出提示框的是实例写法
2019/09/26 PHP
IE6/7/8/9不支持exec的简写方式
2011/05/25 Javascript
JS写的贪吃蛇游戏(个人练习)
2013/07/08 Javascript
javascript实现简单的Map示例介绍
2013/12/23 Javascript
Javascript基础教程之switch语句
2015/01/18 Javascript
原生js实现类似弹窗抖动效果
2015/04/02 Javascript
Juery解决tablesorter中文排序和字符范围的方法
2015/05/06 Javascript
全面了解JavaScript对象进阶
2016/07/19 Javascript
Js得到radiobuttonlist选中值的两种方法(推荐)
2016/08/25 Javascript
js中作用域的实例解析
2017/03/16 Javascript
angularJS利用ng-repeat遍历二维数组的实例代码
2017/06/03 Javascript
详解django模板与vue.js冲突问题
2019/07/07 Javascript
vue学习笔记之作用域插槽实例分析
2020/02/01 Javascript
微信小程序入门之绘制时钟
2020/10/22 Javascript
全面解析Vue中的$nextTick
2020/12/24 Vue.js
python中常用检测字符串相关函数汇总
2015/04/15 Python
Django 配置多站点多域名的实现步骤
2019/05/17 Python
Python 仅获取响应头, 不获取实体的实例
2019/08/21 Python
Python协程操作之gevent(yield阻塞,greenlet),协程实现多任务(有规律的交替协作执行)用法详解
2019/10/14 Python
Python3查找列表中重复元素的个数的3种方法详解
2020/02/13 Python
详解python方法之绑定方法与非绑定方法
2020/08/17 Python
css3使用animation属性实现炫酷效果(推荐)
2020/02/04 HTML / CSS
html5跳转小程序wx-open-launch-weapp踩坑
2020/12/02 HTML / CSS
PHP如何与mysql建立链接
2013/05/05 面试题
.NET面试题:什么是值类型和引用类型
2016/01/12 面试题
产品质量承诺书范文
2014/03/27 职场文书
教师年终个人总结
2015/02/11 职场文书
外贸业务员岗位职责
2015/02/13 职场文书
2015年国庆节慰问信
2015/03/23 职场文书
分享一些Java的常用工具
2021/06/11 Java/Android