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 相关文章推荐
JQuery实现鼠标滑过显示导航下拉列表
Sep 12 Javascript
js获取url参数代码实例分享(JS操作URL)
Dec 13 Javascript
window.location.href中url中数据量太大时的解决方法
Dec 23 Javascript
JQuery教学之性能优化
May 14 Javascript
基于jQuery实现仿淘宝套餐选择插件
Mar 04 Javascript
JS获取文件大小方法小结
Dec 08 Javascript
jQuery设置Cookie及删除Cookie实例分析
Apr 15 Javascript
浅谈vue项目优化之页面的按需加载(vue+webpack)
Dec 11 Javascript
webpack v4 从dev到prd的方法
Apr 02 Javascript
浅谈微信小程序flex布局基础
Sep 10 Javascript
ES6 Generator基本使用方法示例
Jun 06 Javascript
create-react-app开发常用配置教程
Jun 25 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
Admin generator, filters and I18n
2011/10/06 PHP
phpStorm+XDebug+chrome 配置详解
2019/04/01 PHP
图片之间的切换
2006/06/26 Javascript
把JS与CSS写在同一个文件里的书写方法
2007/06/02 Javascript
JavaScript 面向对象编程(1) 基础
2010/05/18 Javascript
深入理解$.each和$(selector).each
2016/05/15 Javascript
JavaScript实现相册弹窗功能(zepto.js)
2016/06/21 Javascript
最全的Javascript编码规范(推荐)
2016/06/22 Javascript
AngularJS 模块化详解及实例代码
2016/09/14 Javascript
jquery实现百叶窗效果
2017/01/12 Javascript
validationEngine 表单验证插件使用实例代码
2017/06/15 Javascript
微信小程序6位或多位验证码密码输入框功能的实现代码
2018/05/29 Javascript
深入浅析Vue全局组件与局部组件的区别
2018/06/15 Javascript
微信小程序自定义组件的实现方法及自定义组件与页面间的数据传递问题
2018/10/09 Javascript
详解vscode中vue代码颜色插件
2018/10/11 Javascript
vue实现文字横向无缝走马灯组件效果的实例代码
2019/04/09 Javascript
使用Vue实现移动端左滑删除效果附源码
2019/05/16 Javascript
js实现倒计时秒杀效果
2020/03/25 Javascript
nodejs处理tcp连接的核心流程
2021/02/26 NodeJs
Python解决走迷宫问题算法示例
2018/07/27 Python
python之super的使用小结
2018/08/13 Python
Python实现剪刀石头布小游戏(与电脑对战)
2019/12/31 Python
如何快速理解python的垃圾回收机制
2020/09/01 Python
快速解决pymongo操作mongodb的时区问题
2020/12/05 Python
详解Python遍历列表时删除元素的正确做法
2021/01/07 Python
日本一家专门经营各种箱包的大型网站:Traveler Store
2016/08/03 全球购物
JavaScript实现页面动态验证码的实现示例
2021/03/23 Javascript
鞋类设计与工艺专业销售求职信
2013/11/01 职场文书
军训教官感言
2014/03/02 职场文书
住宅质量保证书
2014/04/29 职场文书
祖国在我心中演讲稿200字
2014/08/28 职场文书
玩手机检讨书1000字
2014/10/20 职场文书
仓库统计员岗位职责
2015/04/14 职场文书
go:垃圾回收GC触发条件详解
2021/04/24 Golang
MySQL查看表和清空表的常用命令总结
2021/05/26 MySQL
Nginx+Tomcat负载均衡集群的实现示例
2021/10/24 Servers