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 相关文章推荐
js实现一个链接打开两个链接地址的方法
May 12 Javascript
点评js异步加载的4种方式
Dec 22 Javascript
基于canvas实现的绚丽圆圈效果完整实例
Jan 26 Javascript
js基于cookie方式记住返回页面用法示例
May 27 Javascript
不同js异步函数同步的实现方法
May 28 Javascript
JavaScript学习笔记整理_用于模式匹配的String方法
Sep 19 Javascript
JavaScript三种绑定事件方式及相互之间的区别分析
Jan 10 Javascript
详解JavaScript数组过滤相同元素的5种方法
May 23 Javascript
mac上配置Android环境变量的方法
Jul 08 Javascript
vue 使用html2canvas将DOM转化为图片的方法
Sep 11 Javascript
Vue项目总结之webpack常规打包优化方案
Jun 06 Javascript
jQuery实现消息弹出框效果
Dec 10 jQuery
详解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 计算两个时间戳相隔的时间的函数(小时)
2009/12/18 PHP
理解php原理的opcodes(操作码)
2010/10/26 PHP
php使用curl获取header检测开启GZip压缩的方法
2018/08/15 PHP
详解php中生成标准uuid(guid)的方法
2019/04/28 PHP
jQuery 学习6 操纵元素显示效果的函数
2010/02/07 Javascript
详解jQuery插件开发中的extend方法
2013/11/19 Javascript
用javascript替换URL中的参数值示例代码
2014/01/27 Javascript
js获得网页背景色和字体色的方法
2014/03/21 Javascript
使用AngularJS 应用访问 Android 手机的图片库
2015/03/24 Javascript
Bootstrap每天必学之警告框插件
2016/04/26 Javascript
使用JavaScript获取URL中的参数(两种方法)
2016/11/16 Javascript
js实时获取窗口大小变化的实例代码
2016/11/18 Javascript
清除浏览器缓存的几种方法总结(必看)
2016/12/09 Javascript
JavaScript用构造函数如何获取变量的类型名
2016/12/23 Javascript
几行js代码实现自适应
2017/02/24 Javascript
详解如何构建Angular项目目录结构
2017/07/13 Javascript
vue 中filter的多种用法
2018/04/26 Javascript
jQuery实现上下滚动公告栏详细代码
2018/11/21 jQuery
mocha的时序规则讲解
2019/02/16 Javascript
vue+element-ui+axios实现图片上传
2019/08/20 Javascript
js实现翻牌小游戏
2020/07/31 Javascript
[原创]使用豆瓣提供的国内pypi源
2017/07/02 Python
python selenium执行所有测试用例并生成报告的方法
2019/02/13 Python
Python 实现数据结构中的的栈队列
2019/05/16 Python
python3实现单目标粒子群算法
2019/11/14 Python
使用Python解析Chrome浏览器书签的示例
2020/11/13 Python
详解Css3新特性应用之过渡与动画
2017/01/10 HTML / CSS
美国标志性加大尺码时装品牌:Ashley Stewart
2016/12/15 全球购物
GWT的应用有哪两种部署模式
2012/12/21 面试题
新闻系毕业生推荐信
2013/11/16 职场文书
我的梦想演讲稿
2014/04/30 职场文书
村庄环境整治方案
2014/05/15 职场文书
干部个人对照检查材料
2014/08/25 职场文书
装配车间主任岗位职责
2015/04/08 职场文书
少先队中队工作总结
2015/08/14 职场文书
剑指Offer之Java算法习题精讲二叉树的构造和遍历
2022/03/21 Java/Android