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入门—选择器实现隔行变色实例代码
Jan 04 Javascript
原生js实现查找/添加/删除/指定元素的class
Apr 12 Javascript
JavaScript6 let 新语法优势介绍
Jul 15 Javascript
AngularJs基本特性解析(一)
Jul 21 Javascript
轻松掌握JavaScript策略模式
Aug 25 Javascript
浅谈AngularJs指令之scope属性详解
Oct 24 Javascript
利用prop-types第三方库对组件的props中的变量进行类型检测
May 02 Javascript
jquery在vue脚手架中的使用方式示例
Aug 29 jQuery
使用VUE+iView+.Net Core上传图片的方法示例
Jan 04 Javascript
node express使用HTML模板的方法示例
Aug 22 Javascript
JS手写一个自定义Promise操作示例
Mar 16 Javascript
利用Vue的v-for和v-bind实现列表颜色切换
Jul 17 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 fsockopen伪造post与get方法的详解
2013/06/14 PHP
PHP将session信息存储到数据库的类实例
2015/03/04 PHP
PHP文件及文件夹操作之创建、删除、移动、复制
2016/07/13 PHP
php把文件设置为插件的技巧方法
2020/02/03 PHP
JavaScript实现99乘法表及隔行变色实例代码
2016/02/24 Javascript
jquery插件方式实现table查询功能的简单实例
2016/06/06 Javascript
基于JavaScript实现购物网站商品放大镜效果
2016/09/06 Javascript
javascript实现无法关闭的弹框
2016/11/27 Javascript
常见的浏览器Hack技巧整理
2017/06/29 Javascript
vue中el-upload上传图片到七牛的示例代码
2018/10/19 Javascript
微信小程序日历/日期选择插件使用方法详解
2018/12/28 Javascript
利用Electron简单撸一个Markdown编辑器的方法
2019/06/10 Javascript
nodejs简单抓包工具使用详解
2019/08/23 NodeJs
vue-cli3.X快速创建项目的方法步骤
2019/11/14 Javascript
JavaScript canvas实现跟随鼠标事件
2020/02/10 Javascript
js实现简单抽奖功能
2020/11/24 Javascript
JS禁用右键、禁用Ctrl+u、禁用Ctrl+s、禁用F12的实现代码
2020/12/01 Javascript
Python使用Mechanize模块编写爬虫的要点解析
2016/03/31 Python
在python环境下运用kafka对数据进行实时传输的方法
2018/12/27 Python
Python查找最长不包含重复字符的子字符串算法示例
2019/02/13 Python
python 工具 字符串转numpy浮点数组的实现
2020/03/14 Python
python3排序的实例方法
2020/10/20 Python
PyCharm常用配置和常用插件(小结)
2021/02/06 Python
CSS3 input框的实现代码类似Google登录的动画效果
2020/08/04 HTML / CSS
浅析canvas元素的html尺寸和css尺寸对元素视觉的影响
2019/07/22 HTML / CSS
实习生的自我鉴定范文欣赏
2013/11/20 职场文书
《小小竹排画中游》教学反思
2014/02/26 职场文书
安全演讲稿开场白
2014/08/25 职场文书
2014年信息宣传工作总结
2014/12/18 职场文书
经验交流材料格式
2014/12/30 职场文书
2019暑假阅读倡议书
2019/06/24 职场文书
python 下载文件的几种方式分享
2021/04/07 Python
vue项目两种方式实现竖向表格的思路分析
2021/04/28 Vue.js
MySQL如何解决幻读问题
2021/08/07 MySQL
MySQL中int (10) 和 int (11) 的区别
2022/01/22 MySQL
Android超详细讲解组件ScrollView的使用
2022/03/31 Java/Android