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 实现??打印?理
Apr 28 Javascript
JS格式化数字金额用逗号隔开保留两位小数
Oct 18 Javascript
通过jquery 获取URL参数并进行转码
Aug 18 Javascript
浅谈jQuery中的事件
Mar 23 Javascript
基于jquery实现轮播特效
Apr 22 Javascript
前端js文件合并的三种方式推荐
May 19 Javascript
js 获取元素所有兄弟节点的实现方法
Sep 06 Javascript
通过网页查看JS源码中汉字显示乱码的解决方法
Oct 26 Javascript
VUE JS 使用组件实现双向绑定的示例代码
Jan 10 Javascript
bootstrap table表格使用方法详解
Apr 26 Javascript
vuejs实现递归树型菜单组件
Jan 13 Javascript
微信公众号网页分享功能开发的示例代码
May 27 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 和 HTML
2006/10/09 PHP
php.ini中date.timezone设置分析
2011/07/29 PHP
PhpStorm的使用教程(本地运行PHP+远程开发+快捷键)
2020/03/26 PHP
常用简易JavaScript函数
2009/04/09 Javascript
关于this和self的使用说明
2010/08/01 Javascript
Jquery实现仿腾讯娱乐频道焦点图(幻灯片)特效
2015/03/06 Javascript
js中setTimeout()与clearTimeout()用法实例浅析
2015/05/12 Javascript
JS实现兼容性较好的随屏滚动效果
2015/11/09 Javascript
AngularJS中监视Scope变量以及外部调用Scope方法
2016/01/23 Javascript
JavaScript html5 canvas画布中删除一个块区域的方法
2016/01/26 Javascript
Node.js实现JS文件合并小工具
2016/02/02 Javascript
js中new一个对象的过程
2017/02/20 Javascript
Vue.js 单页面多路由区域操作的实例详解
2017/07/17 Javascript
Node.js五大应用性能技巧小结(必须收藏)
2017/08/09 Javascript
HTML5开发Kinect体感游戏的实例应用
2017/09/18 Javascript
js生成word中图片处理方法
2018/01/06 Javascript
nodeJs实现基于连接池连接mysql的方法示例
2018/02/10 NodeJs
AngularJS中重新加载当前路由页面的方法
2018/03/09 Javascript
vue router 通过路由来实现切换头部标题功能
2019/04/24 Javascript
js的Object.assign用法示例分析
2020/03/05 Javascript
JavaScript逻辑运算符相关总结
2020/09/04 Javascript
详解python3中socket套接字的编码问题解决
2017/07/01 Python
基于Django contrib Comments 评论模块(详解)
2017/12/08 Python
python爬虫获取新浪新闻教学
2018/12/23 Python
python开启debug模式的方法
2019/06/27 Python
Python下opencv图像阈值处理的使用笔记
2019/08/04 Python
关于pandas的离散化,面元划分详解
2019/11/22 Python
Python制作简易版小工具之计算天数的实现思路
2020/02/13 Python
Django中ORM找出内容不为空的数据实例
2020/05/20 Python
详解pycharm2020.1.1专业版安装指南(推荐)
2020/08/07 Python
三年级语文教学反思
2014/02/01 职场文书
教师党员批评与自我批评发言稿
2014/10/15 职场文书
优秀班组申报材料
2014/12/25 职场文书
学校教师师德师风承诺书
2015/04/28 职场文书
python正则表达式re.search()的基本使用教程
2021/05/21 Python
使用Navicat Premium工具将oracle数据库迁移到MySQL
2021/05/27 Oracle