fs.readdir递归遍历目录,在外层有知道什么时候完成的办法么?
发布于 6 年前 作者 bi-kai 7147 次浏览 来自 问答

fs.readdir递归遍历目录,在外层有知道什么时候完成的办法么? 比如统计递归的时间,在结束后做一些事情等等。

32 回复

随便找个类库,关键词 walkdir 或 globby

@atian25 之前我用了walk模块,比fs慢了一个数量级。 刚我试了walkdir模块,跟fs.readdirSync效率差不多,不过丢文件呐。 我提了issuse: https://github.com/soldair/node-walkdir/issues/33

我更想知道fs.readdir遍历目录,有什么方式能够知道它的结束呢

对比了fs.readdirSync walkdir walker walk:

windows properties

被遍历的目录包含文件和文件夹数量: 112,033 Files, 14,187 Folders

fs.readdirSync

file:112033; dir:14187; total:126220 22.163s 优点:快 缺点:fs.readdir不知道如何获得递归的完成时间和事件。

walkdir

callback: async:126128 sync:126134 time:24.664s

emitter: file:111947; dir:14181; path:126128 time:13.454s 优点:速度快,贴近原生api速度。 缺点:漏算文件和目录,不能忍。

walker

file:112033; dir:14188; path:126221; time:16.550s 优点:统计准确 缺点:异步模式,与walkdir的emitter对比,这个模块慢了些。 数量统计准确,效率横比也不错,优选

walk

files:112033; dir:14187 108.304s 优点:统计准确 缺点:太慢

fs.readdir不知道如何获得递归的完成时间和事件

没看懂,递归是你自己做的,每一次调用都有 callback,怎么会拿不到

@atian25 我并不知道目录的广度和深度,也就无法知道什么时候是最后一次递归调用啊 目前只能用readdirSync等它自己同步方式递归完毕。

readdirSync 肯定是不用的,同步的方法性能极低。

你是不是对 readdir 有什么误解,它只是单纯的读取指定目录下的文件列表,并不会自动去读取子级目录,要你自己去发起递归的。

我就是用了递归,才不知道什么时候全部递归遍历完。

递归有出口啊,你在出口统计时间不就行了

了解一下,性能上不敢保证,但是能用,源码挺简单,你看下也能自己写

https://github.com/axetroy/walk

@axetroy 我上边测试了walk模块,比较慢,不知道你的如何?

aync/await 写法,把异步方法变成同步写法,这样就知道它什么时候执行完了。

function readdir (src) {
	return new Promise(function (resolve, reject) {
		fs.readdir(src, function (err, files) {
			if (err) {
				reject(err);
			} else {
				resolve(files)
			}
		})
	})
}

async function test () {
	const files = await readdir('xxxxx/xxxx');
}

@dkvirus

node 8后自带promise转换 const {promisify} = require(‘util’);

http://2ality.com/2017/05/util-promisify.html

@bi-kai 你可是试试看性能,贴到上面的帖子我看看 From Noder

@dkvirus 我现在用的就是同步的做法,同步只需要用API里的几个sync方法就行了。我想知道有没有办法在异步方式下,还能知道什么时候遍历完。

@fruit-memory 目录深度和广度未知,如何获得递归的出口呢?我就是纠结在这里。

@bi-kai 我给你写的 async/await + Promise 就是异步方式啊,不是什么异步都是在回调函数里处理的,别看它像同步写法,事实上是异步的。

@fulvaz 长见识了,这样确实更方便,我每次还傻乎乎的自己写 Promise,尴尬~

@dkvirus 哦看错了。如果配合a递归的话,就要嵌套调用你示例中的test方法,我还是没想明白最后一次递归的出口在哪 -_-\\

@bi-kai 说到递归,盗梦空间看过没。

睡着了,做了一个梦; 在这个梦里又做了一个梦,梦里梦,第二层梦; 在第二层梦里又做了一个梦,梦里梦 twice,第三层梦; …

你怎么样才能醒过来?应该是先在第二个梦里醒过来,再在第一个梦里醒过来… 递归的最后一个出口就是第一个梦,细细体会下是不是这个味道…

@bi-kai 用rxjs实现了一个 npm subscribe() 第三个参数就是递归完毕最后出口处 有兴趣话也测试下性能和精确度如何~

ps: 对于无读取权限的目录返回 data.type 值为 unknown

@waitingsong image.png 后边这个undefined了

@bi-kai 我测试没问题呀。你是js还是ts? EntryType是ts的类型

export const enum EntryType {
  unknown = 'unknown',
  notExist = 'notExist',
  file = 'file',
  dir = 'directory',
  block = 'blockDevice',
  char = 'characterDevice',
  fifo = 'fifo',
  socket = 'socket',
  link = 'symbolicLink',
} 

js话可以直接用字符串比较

@bi-kai
js版本的demo

// test.js
const walk = require('rxwalker').default

let dirCount = 0
let fileCount = 0
let entryCout = 0

walk(<path>).subscribe(
  val => {
    switch (val.type) {
      case 'directory':
       dirCount += 1
       break

      case 'file':
       fileCount += 1
       break
    }

    if (val.type !== 'notExists') {
      entryCout += 1
    }
    // console.info('got: ', val.type, '----', val.path)
  },
  err => console.info(err),
  () => {
    console.info(`complete dirs: ${dirCount}, files: ${fileCount}, entries: ${entryCout} `)
  },
)

node test.js

@waitingsong 同一个机器的运行结果:

fs.readdirSync

scan folder finished, dir num:10395, file num:92016, deal time: 00:00:26

rxwalker

scan folder finished, dir num:10396, file num:92016, deal time: 00:01:41 准确性没问题,效率比同步遍历慢。

慢了几倍多。看来异步模式加上 rxjs 事件开销比较大。 不过我这个基于事件流的方式容易实现各种过滤控制(包括文件名、文件父目录名,搜索深度),以及中途终止扫描,扩展也方便(直接pipe流就行了都不用改模块)

10395 vs 10396 相差一个文件

@waitingsong 差的是个文件夹,应该是把传入的根目录算进去了 目前我测的几个模块,walker和你的接近,但都没有中途停止的功能,这是个突出的点。 我目前是一个线程遍历大量目录和文件,更在乎效率,不同业务很可能就需要终止的功能。

对的。传入的路径作为 depth0 计算入目录类型的。

大致看,如果一个入口差异 10ms,10万*10ms= 100s. 同步模式不进入事件循环,在循环效率上比异步高。不过在系统角度看异步不阻塞主线程。各有特点。 我这个模块主要特点是对于循环查询流程可控——可过滤目录入口/文件输出,可控制查询深度,可随时中断流程。 那个 dirFilterCb 回调函数支持的返回值类型不但是同步的(过滤后的)数组,还可以返回 Promise<数组>,当然 Observable<数组>也行,这样花样可多了,你可以拿传入的数据去做异步请求过滤,甚至还可以拿去排序(比如把目录排在前面,文件排在后面)。不过速度比较慢,我看看能否优化下效率,比如提供可选同步查询模式

readdir第一层正常读取,深层目录的递归返回promise,push到一个数组里;然后用Promise.all去执行

回到顶部