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 相关文章推荐
prototype Element学习笔记(篇一)
Oct 26 Javascript
jQuery 添加/移除CSS类实现代码
Feb 11 Javascript
工作中常用到的JS表单验证代码(包括例子)
Nov 11 Javascript
网页前端优化之滚动延时加载图片示例
Jul 13 Javascript
JavaScript插件化开发教程(六)
Feb 01 Javascript
jquery性能优化高级技巧
Aug 24 Javascript
json实现添加、遍历与删除属性的方法
Jun 17 Javascript
省市二级联动小案例讲解
Jul 24 Javascript
详解Javascript ES6中的箭头函数(Arrow Functions)
Aug 24 Javascript
微信小程序开发中的疑问解答汇总
Jul 03 Javascript
基于vue如何发布一个npm包的方法步骤
May 15 Javascript
layui table 表格上添加日期控件的两种方法
Sep 28 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开启openssl的方法
2014/05/15 PHP
php打开本地exe程序,js打开本地exe应用程序,并传递相关参数方法
2018/02/06 PHP
php使用QueryList轻松采集js动态渲染页面方法
2018/09/11 PHP
js中将字符串转换成json的三种方式
2011/01/12 Javascript
js统计页面的来访次数实现代码
2014/05/09 Javascript
关于JS数组追加数组采用push.apply的问题
2014/06/09 Javascript
z-blog SyntaxHighlighter 长代码无法换行解决办法(jquery)
2014/11/16 Javascript
js判断滚动条是否已到页面最底部或顶部实例
2014/11/20 Javascript
浅谈Javascript中深复制
2014/12/01 Javascript
JavaScript的面向对象编程基础
2015/08/13 Javascript
js实现四舍五入完全保留两位小数的方法
2016/08/02 Javascript
微信小程序radio组件使用详解
2018/01/31 Javascript
jQuery中each方法的使用详解
2018/03/18 jQuery
JS中实现一个下载进度条及播放进度条的代码
2019/06/10 Javascript
vue的注意规范之v-if 与 v-for 一起使用教程
2019/08/04 Javascript
three.js 利用uv和ThreeBSP制作一个快递柜功能
2020/08/18 Javascript
js删除对象中的某一个字段的方法实现
2021/01/11 Javascript
[03:11]完美世界DOTA2联赛PWL DAY8集锦
2020/11/09 DOTA
Python发送邮件功能示例【使用QQ邮箱】
2018/12/04 Python
Spring Cloud Feign高级应用实例详解
2019/12/10 Python
python GUI库图形界面开发之pyinstaller打包python程序为exe安装文件
2020/02/26 Python
Django实现后台上传并显示图片功能
2020/05/29 Python
Java byte数组操纵方式代码实例解析
2020/07/22 Python
美国药妆网站:EDCskincare.com(防晒、痤疮、抗衰老等)
2017/04/28 全球购物
怎样声明子类
2013/07/02 面试题
新东网科技Java笔试题
2012/07/13 面试题
天游软件面试
2013/11/23 面试题
求职简历自荐信范文
2013/10/21 职场文书
消防先进事迹材料
2014/02/10 职场文书
《歌唱二小放牛郎》教学反思
2014/04/19 职场文书
工作散漫检讨书
2014/09/16 职场文书
夫妻分居协议书范本
2014/11/28 职场文书
先进党支部申报材料
2014/12/24 职场文书
关于环保的广播稿
2015/12/17 职场文书
学校就业保障协议书
2019/06/24 职场文书
python机器学习创建基于规则聊天机器人过程示例详解
2021/11/02 Python