节点 fs.readdir 在文件过多的文件夹中冻结

2024-01-06

在 Node.js 中我必须读取文件夹中的文件,并为每个文件获取文件处理程序信息,这是我使用的最简单的实现fs.readdir:

FileServer.prototype.listLocal = function (params) {
            var self = this;
            var options = {
                limit: 100,
                desc: 1
            };
            // override defaults
            for (var attrname in params) { options[attrname] = params[attrname]; }

            // media path is the media folder
            var mediaDir = path.join(self._options.mediaDir, path.sep);
            return new Promise((resolve, reject) => {
                fs.readdir(mediaDir, (error, results) => {
                    if (error) {
                        self.logger.error("FileServer.list error:%s", error);
                        return reject(error);
                    } else { // list files
                        // cut to max files
                        results = results.slice(0, options.limit);
                        // filter default ext
                        results = results.filter(item => {
                            return (item.indexOf('.mp3') > -1);
                        });
                        // format meta data
                        results = results.map(file => {
                            var filePath = path.join(self._options.mediaDir, path.sep, file);
                            var item = {
                                name: file,
                                path: filePath
                            };
                            const fd = fs.openSync(filePath, 'r');
                            var fstat = fs.fstatSync(fd);
                            // file size in bytes
                            item.size = fstat.size;
                            item.sizehr = self.formatSizeUnits(fstat.size);
                            // "Birth Time" Time of file creation. Set once when the file is created. 
                            item.birthtime = fstat.birthtime;
                            // "Modified Time" Time when file data last modified.
                            item.mtime = fstat.mtime;
                            // "Access Time" Time when file data last accessed.
                            item.atime = fstat.atime;
                            item.timestamp = new Date(item.mtime).getTime();
                            item.media_id = path.basename(filePath, '.mp3');

                            fs.closeSync(fd);//close file
                            return item;
                        });
                        if (options.desc) { // sort by most recent
                            results.sort(function (a, b) {
                                return b.timestamp - a.timestamp;
                            });
                        } else { // sort by older
                            results.sort(function (a, b) {
                                return a.timestamp - b.timestamp;
                            });
                        }
                        return resolve(results);
                    }
                })
            });
        }

这样对于每个文件我都会得到一个项目数组

{
  "name": "sample121.mp3",
  "path": "/data/sample121.mp3",
  "size": 5751405,
  "sizehr": "5.4850 MB",
  "birthtime": "2018-10-08T15:26:08.397Z",
  "mtime": "2018-10-08T15:26:11.650Z",
  "atime": "2018-10-10T09:01:48.534Z",
  "timestamp": 1539012371650,
  "media_id": "sample121"
}

也就是说,问题是 Node.js 已知fs.readdir当要列出的文件夹包含大量文件(例如从一万到十万甚至更多)时,可能会冻结节点 I/O 循环。 这是一个已知问题 - 请参阅here https://github.com/tagspaces/tagspaces/issues/738了解更多信息。 也有改善计划fs.readdir在某种程度上,比如流媒体 - 请参阅here https://github.com/nodejs/node/issues/583对这个。

与此同时,我正在寻找类似的补丁,因为我的文件夹非常大。 由于问题是事件循环被冻结,有人提出了一个解决方案process.nextTick,我在这里合奏了

FileServer.prototype.listLocalNextTick = function (params) {
            var self = this;
            var options = {
                limit: 100,
                desc: 1
            };
            // override defaults
            for (var attrname in params) { options[attrname] = params[attrname]; }

            // media path is the media folder
            var mediaDir = path.join(self._options.mediaDir, path.sep);
            return new Promise((resolve, reject) => {
                var AsyncArrayProcessor = function (inArray, inEntryProcessingFunction) {
                    var elemNum = 0;
                    var arrLen = inArray.length;
                    var ArrayIterator = function () {
                        inEntryProcessingFunction(inArray[elemNum]);
                        elemNum++;
                        if (elemNum < arrLen) process.nextTick(ArrayIterator);
                    }
                    if (elemNum < arrLen) process.nextTick(ArrayIterator);
                }
                fs.readdir(mediaDir, function (error, results) {
                    if (error) {
                        self.logger.error("FileServer.list error:%s", error);
                        return reject(error);
                    }
                    // cut to max files
                    results = results.slice(0, options.limit);
                    // filter default ext
                    results = results.filter(item => {
                        return (item.indexOf('.mp3') > -1);
                    });
                    var ProcessDirectoryEntry = function (file) {
                        // This may be as complex as you may fit in a single event loop
                        var filePath = path.join(self._options.mediaDir, path.sep, file);
                        var item = {
                            name: file,
                            path: filePath
                        };
                        const fd = fs.openSync(filePath, 'r');
                        var fstat = fs.fstatSync(fd);
                        // file size in bytes
                        item.size = fstat.size;
                        item.sizehr = self.formatSizeUnits(fstat.size);
                        // "Birth Time" Time of file creation. Set once when the file is created. 
                        item.birthtime = fstat.birthtime;
                        // "Modified Time" Time when file data last modified.
                        item.mtime = fstat.mtime;
                        // "Access Time" Time when file data last accessed.
                        item.atime = fstat.atime;
                        item.timestamp = new Date(item.mtime).getTime();
                        item.media_id = path.basename(filePath, '.mp3');
                        // map to file item
                        file = item;
                    }//ProcessDirectoryEntry
                    // LP: fs.readdir() callback is finished, event loop continues...
                    AsyncArrayProcessor(results, ProcessDirectoryEntry);
                    if (options.desc) { // sort by most recent
                        results.sort(function (a, b) {
                            return b.timestamp - a.timestamp;
                        });
                    } else { // sort by older
                        results.sort(function (a, b) {
                            return a.timestamp - b.timestamp;
                        });
                    }
                    return resolve(results);
                });
            });
        }//listLocalNextTick

这似乎避免了原来的问题,但我无法再将文件列表映射到我之前使用文件处理程序执行的项目,因为在运行时AsyncArrayProcessor在文件列表上,因此ProcessDirectoryEntry在每个文件条目上的异步性质process.nextTick导致我无法取回results数组修改如上listLocal我刚刚进行迭代的函数array.map of the results大批。 如何打补丁listLocalNextTick表现得像listLocal但保留process.nextTick方法?

[UPDATE]

根据建议的解决方案,这是迄今为止最好的实现:

       /**
         * Scan files in directory
         * @param {String} needle 
         * @param {object} options 
         * @returns {nodeStream}
         */
        scanDirStream : function(needle,params) {
            var options = {
                type: 'f',
                name: '*'
            };
            for (var attrname in params) { options[attrname] = params[attrname]; }
            return new Promise((resolve, reject) => {
                var opt=[needle];
                for (var k in options) {
                    var v = options[k];
                    if (!Util.empty(v)) {
                        opt.push('-' + k);
                        opt.push(v);
                    }
                };
                var data='';
                var listing = spawn('find',opt)
                listing.stdout.on('data', _data => {
                    var buff=Buffer.from(_data, 'utf-8').toString();
                    if(buff!='') data+=buff;
                })
                listing.stderr.on('data', error => {
                    return reject(Buffer.from(error, 'utf-8').toString());
                });
                listing.on('close', (code) => {
                    var res = data.split('\n');
                    return resolve(res);
                });
            });

使用示例:

scanDirStream(mediaRoot,{
        name: '*.mp3'
    })
    .then(results => {
        console.info("files:%d", results);
    })
    .catch(error => {
        console.error("error %s", error);
    });

最终可以修改为在每次添加一个刻度回调stdout.on在监听目录中获取新文件时发出事件。


我已经为它创建了一个围绕 find 的包装器,但您可以以相同的方式使用 dir 或 ls 。

const { spawn } = require('child_process');

/**
 * findNodeStream
 * @param {String} dir 
 * @returns {nodeStream}
 */
const findNodeStream = (dir,options) => spawn('find',[dir,options].flat().filter(x=>x));

/**
 * Usage Example:
  let listing = findNodeStream('dir',[options])
  listing.stdout.on('data', d=>console.log(d.toString()))
  listing.stderr.on('data', d=>console.log(d.toString()))
  listing.on('close', (code) => {
    console.log(`child process exited with code ${code}`);
  });
*/

这允许您流式传输分块的目录,而不是像 fs.readdir 那样流式传输整个目录。

重要的

NodeJS > 12.11.1 将具有异步 readdir 支持 登陆 cbd8d71 (https://github.com/nodejs/node/commit/cbd8d715b2286e5726e6988921f5c870cbf74127 https://github.com/nodejs/node/commit/cbd8d715b2286e5726e6988921f5c870cbf74127)作为 fs{Promises}.opendir(),它返回一个 fs.Dir,它公开一个异步迭代器。多田

https://nodejs.org/api/fs.html#fs_fspromises_opendir_path_options https://nodejs.org/api/fs.html#fs_fspromises_opendir_path_options

const fs = require('fs');

async function print(path) {
  const dir = await fs.promises.opendir(path);
  for await (const dirent of dir) {
    console.log(dirent.name);
  }
}
print('./').catch(console.error);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

节点 fs.readdir 在文件过多的文件夹中冻结 的相关文章

随机推荐

  • mySQL:基于不同条件的多个 COUNT()

    好吧 所以我想做的是根据不同的条件在同一个表上执行不同的 COUNT 而不需要嵌套查询 效率 或子查询 因此可以将其制作成视图 这可能吗 示例 表具有日期列 查询应该能够生成固定日期之前和之后的行数计数 您正在使用 MySQL 因此您可以利
  • 从 PHP 开发人员的角度来看,MySQL 与 C#

    我知道使用 PHP 我可以使用 mysql query sql 和 mysql fetch array 结果 获取一些 MySQL 数据并将其放入数组中 在 C 中如何实现这一点 以便我可以将数据放置在数据网格中 这可能是您将看到的填充 D
  • Android中如何存储大量音频文件?

    我正在开发一个需要我播放大量音频文件的应用程序 每个文件的大小约为 100KB 目前我有 220 个文件 将来可能会增加 如果我将这些文件放在 res raw 文件夹中 那么我最终会得到一个非常大的 APK 文件 在 Android 中存储
  • 我应该为移动网站使用什么文档类型?

    我正在从头开始创建网站的移动版本 但我不确定应该使用什么文档类型 好像没有一个明确的标准 是吗 在许多移动网站上我看到 在其他人身上我发现 有人能为我指出正确的移动网页设计方向吗 我想支持大多数现代手持设备 例如 iPhone
  • 以 15 分钟为间隔排列来自销售人员的呼叫数据

    我是 python 和 pandas 以及 stackoverflow 的新手 所以我对我提前犯的任何错误表示歉意 我有这个数据框 df pd DataFrame data Donald Trump German 2021 9 23 14
  • 执行 ApplicationCommands.Close 时实际发生的情况

    我想标题中的问题已经很清楚了 当我打电话时会发生什么 ApplicationCommands Close Execute null null 来自我的 viewmodel 类 我有一个显示用户控件的模型对话框 我有一个命令绑定到用户控件中的
  • Keras 中的 LSTM 序列预测仅输出输入中的最后一步

    我目前正在使用 Keras 使用 Tensorflow 作为后端 我有一个 LSTM 序列预测模型 如下所示 我用它来预测数据系列中的一步 输入 30 个步骤 每个步骤有 4 个特征 输出预测步骤 31 model Sequential m
  • SQL Server:检索存储过程中的自动递增ID?

    我的数据库有一个带有自动递增主键标识 ID 的父表和一个普通的 TIMESTAMP 列 我有带有外键的子表 该外键引用父 ID 列 我想编写一个存储过程 将新列插入到父数据库和子数据库中 如何将子 ID 列设置为等于新的自动递增父 ID 列
  • 单击导航抽屉的项目不会打开片段

    我想使用 Android Studio v 3 5 的默认导航抽屉活动 创建此默认活动 新项目 gt 导航抽屉活动 后 我启动了此模板 如果我单击导航菜单的图标之一 例如 图库 NavHost 的当前片段不会更改 据我了解以下部分 http
  • 如何在运行时在react-native-mapbox-gl中显示/隐藏栅格图层(可见性属性可见/无)

    我在地图初始化中设置了自定义样式 url 喜欢
  • 在什么情况下你会选择使用 Map 而不是普通数组? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 当最好使用 Map 而不是普通数组时 我很难找到真实的情况 只是每次使用似乎都比较困难 你实验过合适的案例吗 谢谢 As MDN 简要介绍了 h
  • 在 IE6 中链接 CSS 类 - 试图找到 jQuery 解决方案?

    tl dr 有人知道如何使用 jQuery 或类似的方法为 IE6 应用链式类吗 Right 也许我问的是不可能的事 我认为自己对 Javscript 和 jQuery 相当陌生 但话虽这么说 我最近编写了一些相当复杂的代码 所以我肯定会到
  • PWA 有没有办法访问主机设备的电话号码?

    我计划为 Android 设备开发一款渐进式 Web 应用程序 一个主要问题是 PWA 是否可以访问主机电话号码 我们的业务是向业务合作伙伴提供廉价手机 如果我们的 PWA 可以访问设备的电话号码 我们就可以更轻松地进行两因素身份验证 我认
  • 如何为外部命令提供输入?

    据我了解 Scala 可以运行系统命令并接收其输出 我正在为我拥有的系统命令编写一个网络客户端 因此我需要执行该命令 接收输出 根据输出我可以给它一个命令 我想继续这样做 直到用户终止该命令 我已经阅读了一些有关 ProcessIO 的内容
  • 在 SQL 中,使用 DISTINCT 如何影响性能?

    我试图选择一个不同的列表 其中在多个字段上创建重复项 例如 SELECT tablename field1Date tablename field2Number tablename field3Text FROM tablename 将分别
  • 实现滚动锚定时元素“轻微抽搐”

    编辑 令人尴尬的是 我最初在 Chrome 上看到了这个问题 但问题是因为我稍微缩小了浏览器 导致子像素元素大小调整 如果元素是完整像素高度 则效果很好 然而 我相信这仍然是一个问题 因为 Firefox 渲染子像素元素的方式似乎与 Chr
  • os.Logger (os_log) 忽略新的隐私字符串插值

    在 iOS 14 模拟器上使用以下代码 所有日志语句都以明文形式打印 没有发生任何编辑或散列 在 Xcode 控制台和 Mac 上的 Console app 中都可以 还需要什么来测试编辑是否有效吗 let email email prot
  • Hibernate 在外键字段中插入空值

    我有 2 个简单的域对象 如下所示 使用 MYSQL DB Entity Table name Product public class Product Id Column name productId GeneratedValue pro
  • 使用 nginx 1.0 动态 proxy_pass 到 $var

    我正在尝试根据环境变量将请求代理到不同的目标 我的方法是将目标 url 放入自定义变量 target 中 并将其传递给 proxy pass 但使用 proxy pass 变量似乎不起作用 这个简单的配置会导致 nginx 发出 502 B
  • 节点 fs.readdir 在文件过多的文件夹中冻结

    在 Node js 中我必须读取文件夹中的文件 并为每个文件获取文件处理程序信息 这是我使用的最简单的实现fs readdir FileServer prototype listLocal function params var self