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 相关文章推荐
会自动逐行上升的文本框
Jun 30 Javascript
JavaScript 给汉字排序实例代码
Jun 28 Javascript
jQuery '行 4954 错误: 不支持该属性或方法' 的问题解决方法
Jan 19 Javascript
jquery $.each()使用探讨
Sep 23 Javascript
Javascript封装DOMContentLoaded事件实例
Jun 12 Javascript
跟我学习javascript的call(),apply(),bind()与回调
Nov 16 Javascript
Bootstrap三种表单布局的使用方法
Jun 21 Javascript
卸载安装Node.js与npm过程详解
Aug 15 Javascript
Javascript生成带参数的二维码示例
Oct 10 Javascript
微信页面倒计时代码(解决safari不兼容date的问题)
Dec 13 Javascript
原生小程序封装跑马灯效果
Oct 21 Javascript
WebStorm无法正确识别Vue3组合式API的解决方案
Feb 18 Vue.js
详解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网页后退不再出现过期
2007/03/08 PHP
并发下常见的加锁及锁的PHP具体实现代码
2010/10/12 PHP
利用PHP生成CSV文件简单示例
2016/12/21 PHP
js文字滚动停顿效果代码
2008/06/28 Javascript
JavaScript 判断判断某个对象是Object还是一个Array
2010/01/28 Javascript
使用js声明数组,对象在jsp页面中(获得ajax得到json数据)
2013/11/05 Javascript
js触发select onchange事件的小技巧
2014/08/05 Javascript
关于编写性能高效的javascript事件的技术
2014/11/28 Javascript
js数组的操作指南
2014/12/28 Javascript
javascript实现简单的页面右下角提示信息框
2015/07/31 Javascript
JS中多种方式创建对象详解
2016/03/22 Javascript
Vue.js双向绑定操作技巧(初级入门)
2016/12/27 Javascript
javascript实现页面滚屏效果
2017/01/17 Javascript
解析jquery easyui tree异步加载子节点问题
2017/03/08 Javascript
JavaScript异步上传图片文件的实例代码
2017/07/04 Javascript
vue进行图片的预加载watch用法实例讲解
2018/02/07 Javascript
JS/jQuery实现DIV延时几秒后消失或显示的方法
2018/02/12 jQuery
vuex实现及简略解析(小结)
2019/03/01 Javascript
JS学习笔记之贪吃蛇小游戏demo实例详解
2019/05/29 Javascript
[41:17]完美世界DOTA2联赛PWL S3 access vs CPG 第二场 12.13
2020/12/17 DOTA
python定时检查某个进程是否已经关闭的方法
2015/05/20 Python
Python缩进和冒号详解
2016/06/01 Python
Python实现针对给定字符串寻找最长非重复子串的方法
2018/04/21 Python
python opencv实现图像边缘检测
2019/04/29 Python
Python3.6+selenium2.53.6自动化测试_读取excel文件的方法
2019/09/06 Python
python3 BeautifulSoup模块使用字典的方法抓取a标签内的数据示例
2019/11/28 Python
使用python实现下载我们想听的歌曲,速度超快
2020/07/09 Python
巴西本土电商平台:Americanas
2020/06/21 全球购物
网络工程师个人的自我评价范文
2013/10/01 职场文书
医药大学生求职简历的自我评价
2013/10/17 职场文书
教师求职推荐信范文
2013/11/20 职场文书
《浅水洼里的小鱼》听课反思
2014/02/28 职场文书
2015社区个人工作总结范文
2015/05/13 职场文书
2019个人半年工作总结
2019/06/21 职场文书
教你怎么用python selenium实现自动化测试
2021/05/27 Python
Win11 PC上的Outlook搜索错误怎么办?
2022/07/15 数码科技