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 相关文章推荐
createElement与createDocumentFragment的点点区别小结
Dec 19 Javascript
使用Post提交时须将空格转换成加号的解释
Jan 14 Javascript
JQGrid的用法解析(列编辑,添加行,删除行)
Nov 08 Javascript
利用javascript实现禁用网页上所有文本框,下拉菜单,多行文本域
Dec 14 Javascript
JavaScript之Object类型介绍
Apr 01 Javascript
基于JavaScript实现购物车功能
Feb 07 Javascript
微信小程序中显示html格式内容的方法
Apr 25 Javascript
JS实现新建文件夹功能
Jun 17 Javascript
详解Node.js一行命令上传本地文件到服务器
Apr 22 Javascript
vue-cli3使用mock数据的方法分析
Mar 16 Javascript
解决vue prop传值default属性如何使用,为何不生效的问题
Sep 21 Javascript
JavaScript中时间格式化新思路toLocaleString()
Nov 07 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的5个入手程序
2006/11/23 PHP
php验证session无效的解决方法
2014/11/04 PHP
PHP数组操作――获取数组最后一个值的方法
2015/04/14 PHP
在CentOS上搭建LAMP+vsftpd环境的简单指南
2015/08/01 PHP
thinkphp3.2实现上传图片的控制器方法
2016/04/28 PHP
javascript 显示当前系统时间代码
2009/12/28 Javascript
JavaScript 编写匿名函数的几种方法
2010/02/21 Javascript
jquery.validate使用攻略 第二部
2010/07/01 Javascript
浏览器打开层自动缓慢展开收缩实例代码
2013/07/04 Javascript
页面按钮禁用与解除禁用的方法
2014/02/19 Javascript
基于JS代码实现图片在页面中旋转效果
2016/06/16 Javascript
Kendo Grid editing 自定义验证报错提示的解决方法
2016/11/18 Javascript
微信小程序如何像vue一样在动态绑定类名
2018/04/17 Javascript
vue实现侧边栏导航效果
2019/10/21 Javascript
vue 使用localstorage实现面包屑的操作
2020/11/16 Javascript
linux服务器快速卸载安装node环境(简单上手)
2021/02/22 Javascript
[01:56]《DOTA2》中文配音CG
2013/04/22 DOTA
Python的__builtin__模块中的一些要点知识
2015/05/02 Python
教你用Type Hint提高Python程序开发效率
2016/08/08 Python
读取json格式为DataFrame(可转为.csv)的实例讲解
2018/06/05 Python
Python之用户输入的实例
2018/06/22 Python
Python制作exe文件简单流程
2019/01/24 Python
python 为什么说eval要慎用
2019/03/26 Python
Perfume’s Club澳大利亚官网:西班牙领先的在线美容店
2021/02/01 全球购物
致接力运动员广播稿
2014/02/17 职场文书
实习评语大全
2014/04/26 职场文书
幼儿园小班见习报告
2014/10/31 职场文书
任命通知范文
2015/04/21 职场文书
困难补助申请报告
2015/05/19 职场文书
赵氏孤儿观后感
2015/06/09 职场文书
田径运动会广播稿
2015/08/19 职场文书
在校大学生才艺比赛策划书怎么写?
2019/08/26 职场文书
《卧薪尝胆》读后感3篇
2019/12/26 职场文书
Python中第三方库Faker的使用详解
2022/04/02 Python
pytorch分类模型绘制混淆矩阵以及可视化详解
2022/04/07 Python
Navicat Premium自定义 sql 标签的创建方式
2022/09/23 数据库