加载中...

遍历目录


遍历目录是操作文件时的一个常见需求。比如写一个程序,需要找到并处理指定目录下的所有JS文件时,就需要遍历整个目录。

递归算法

遍历目录时一般使用递归算法,否则就难以编写出简洁的代码。递归算法与数学归纳法类似,通过不断缩小问题的规模来解决问题。以下示例说明了这种方法。

  1. function factorial(n) {
  2. if (n === 1) {
  3. return 1;
  4. } else {
  5. return n * factorial(n - 1);
  6. }
  7. }

上边的函数用于计算N的阶乘(N!)。可以看到,当N大于1时,问题简化为计算N乘以N-1的阶乘。当N等于1时,问题达到最小规模,不需要再简化,因此直接返回1。

陷阱: 使用递归算法编写的代码虽然简洁,但由于每递归一次就产生一次函数调用,在需要优先考虑性能时,需要把递归算法转换为循环算法,以减少函数调用次数。

遍历算法

目录是一个树状结构,在遍历时一般使用深度优先+先序遍历算法。深度优先,意味着到达一个节点后,首先接着遍历子节点而不是邻居节点。先序遍历,意味着首次到达了某节点就算遍历完成,而不是最后一次返回某节点才算数。因此使用这种遍历方式时,下边这棵树的遍历顺序是A > B > D > E > C > F

  1. A
  2. / \
  3. B C
  4. / \ \
  5. D E F

同步遍历

了解了必要的算法后,我们可以简单地实现以下目录遍历函数。

  1. function travel(dir, callback) {
  2. fs.readdirSync(dir).forEach(function (file) {
  3. var pathname = path.join(dir, file);
  4. if (fs.statSync(pathname).isDirectory()) {
  5. travel(pathname, callback);
  6. } else {
  7. callback(pathname);
  8. }
  9. });
  10. }

可以看到,该函数以某个目录作为遍历的起点。遇到一个子目录时,就先接着遍历子目录。遇到一个文件时,就把文件的绝对路径传给回调函数。回调函数拿到文件路径后,就可以做各种判断和处理。因此假设有以下目录:

  1. - /home/user/
  2. - foo/
  3. x.js
  4. - bar/
  5. y.js
  6. z.css

使用以下代码遍历该目录时,得到的输入如下。

  1. travel('/home/user', function (pathname) {
  2. console.log(pathname);
  3. });
  4. ------------------------
  5. /home/user/foo/x.js
  6. /home/user/bar/y.js
  7. /home/user/z.css

异步遍历

如果读取目录或读取文件状态时使用的是异步API,目录遍历函数实现起来会有些复杂,但原理完全相同。travel函数的异步版本如下。

  1. function travel(dir, callback, finish) {
  2. fs.readdir(dir, function (err, files) {
  3. (function next(i) {
  4. if (i < files.length) {
  5. var pathname = path.join(dir, files[i]);
  6. fs.stat(pathname, function (err, stats) {
  7. if (stats.isDirectory()) {
  8. travel(pathname, callback, function () {
  9. next(i + 1);
  10. });
  11. } else {
  12. callback(pathname, function () {
  13. next(i + 1);
  14. });
  15. }
  16. });
  17. } else {
  18. finish && finish();
  19. }
  20. }(0));
  21. });
  22. }

这里不详细介绍异步遍历函数的编写技巧,在后续章节中会详细介绍这个。总之我们可以看到异步编程还是蛮复杂的。


还没有评论.