如何在node.js中获取目录大小而无需递归遍历目录?

22

如何在node.js中获取一个目录的大小,而不需要递归遍历该目录下所有子文件夹?

例如:

var fs = require('fs');
fs.statSync('path/to/dir');

会返回一个像这样的对象:

{ dev: 16777220,
  mode: 16877,
  nlink: 6,
  uid: 501,
  gid: 20,
  rdev: 0,
  blksize: 4096,
  ino: 62403939,
  size: 204,
  blocks: 0,
  atime: Mon May 25 2015 20:54:53 GMT-0400 (EDT),
  mtime: Mon May 25 2015 20:09:41 GMT-0400 (EDT),
  ctime: Mon May 25 2015 20:09:41 GMT-0400 (EDT) }

但是size属性并不是目录及其子项(即其中包含的文件的总和)的大小。

难道没有办法获取目录的大小(包括其中文件的大小),而不需要递归查找其子项的大小,然后将其总和起来吗?

我基本上想要做的是相当于执行 du -ksh my-directory,但如果给定的目录非常大(例如/),那么递归地获取真正的目录大小就需要花费很长时间。


1
我知道 du -ksh / 需要很长时间,所以也许这个问题是...无意义的...我希望在这里有一个我所缺失的 Linux 东西。 - user772401
2
du 就是做这个的,所以不,你无法绕过它。 - Ry-
2
据我所知,目录不会跟踪其下所有文件的累积大小,因此获取累积大小的唯一方法是进行递归并相加。这不是一个快速操作。 - jfriend00
1
“without recursively going through directory”是什么意思?您是指(A)一个库函数,可以通过一行调用来获取答案,而无需自己进行递归,还是(B)实现一个函数,可以在不进行任何递归函数调用的情况下遍历树,或者(C)获取答案,而无需任何人(您或调用的函数)迭代目录内容?如果是(C),那是不可能的,正如@jfriend00所指出的那样。即使是du也会内部迭代所有文件。请澄清。 - Inigo
5个回答

19

我使用这个简单的async/await + fs Promises API(Node.js v14+)解决方案...它不依赖于外部库或生成新进程,这很好:

const path = require('path');
const { readdir, stat } = require('fs/promises');

const dirSize = async directory => {
  const files = await readdir( directory );
  const stats = files.map( file => stat( path.join( directory, file ) ) );

  return ( await Promise.all( stats ) ).reduce( ( accumulator, { size } ) => accumulator + size, 0 );
}

使用方法:

( async () => {
  const size = await dirSize( '/path/to/directory' );
  console.log( size );
} )();

这种方法不使用任何循环结构来遍历目录,虽然它在映射/缩小数组。其他解决方案只是将递归抽象化到NPM包/C代码后面,所以应该都可以...


更新: 我以前的用例是基于上面的解决方案获取目录而不递归遍历子目录... 重新阅读问题后清楚原始发布者也想要子目录的大小。

如果有人正在寻找这个技巧,这应该就是解决方案;但从技术上讲,它并没有避免递归。感谢评论@Inigo!

const { readdir, stat } = require('fs/promises');
const { join } = require('path');

const dirSize = async dir => {
  const files = await readdir( dir, { withFileTypes: true } );

  const paths = files.map( async file => {
    const path = join( dir, file.name );

    if ( file.isDirectory() ) return await dirSize( path );

    if ( file.isFile() ) {
      const { size } = await stat( path );
      
      return size;
    }

    return 0;
  } );

  return ( await Promise.all( paths ) ).flat( Infinity ).reduce( ( i, size ) => i + size, 0 );
}

使用方法:

( async () => {
  const size = await dirSize( '/path/to/directory' );
  console.log( size );
} )();

1
@bluepuma77 哈哈,好问题!在你的评论之后,我更新了答案,因此它之前是未定义的 :P 感谢你的指出。 - Andrew Odri
2
这个解决方案根本不会递归遍历目录,因此如果目录有子目录,则会给出错误的答案,这可能是最重要的使用情况。这会导致被点踩,如果你修复它,我会撤回点踩。 - Inigo
啊,@Inigo 发现得好!我错过了 OP 的问题意图。希望额外的解决方案能帮助其他寻找嵌套子目录大小的帖子 :) - Andrew Odri
1
我会完全放弃你的初始解决方案。它是错误的,因为按定义,目录的大小必须包括任何包含的目录的大小。至于OP的“不递归地浏览目录”,请看我在问题下的评论。如果他们的意思是(B),那么修改你的解决方案以使用循环和堆栈而不是递归并不难。如果你从未这样做过,那么这是成为专业程序员的好练习 :) - Inigo
目录的大小必须包括任何包含的目录的大小...我不同意这个观点...我回复这个帖子是因为我需要为一个生成纯文件大小哈希的第三方应用程序构建软件包;我搜索了解决方案,这个帖子出现了,我决定贡献一下,因为解决方案不在那里。选择很重要;错误的问题也需要解决 :) 我还会争辩说,使用一个for循环来无限递归地遍历一个目录比使用一个reducer更加传统的“递归” ¯_(ツ)_/¯ - Andrew Odri
显示剩余2条评论

7

你可以在目标目录上启动一个 du 命令,但是正如你所说的那样,第一次可能会非常慢。你可能不知道的是,du 的结果似乎被缓存了:

$ time du -sh /var
13G /var
du -sh /var  0.21s user 0.66s system 9% cpu 8.930 total
$ time du -sh /var
13G /var
du -sh /var  0.11s user 0.34s system 98% cpu 0.464 total

最初需要8秒,然后只需要0.4秒

因此,如果您的目录不经常更改,只使用du可能是最简单的方法。

另一种解决方案是将其存储在缓存层中,这样您就可以监视根目录的更改,然后计算文件夹的大小,将其存储在缓存中,并在需要时直接提供服务。要执行此操作,您可以使用NodeJS的观察功能,但会有一些跨平台问题,因此像chokidar这样的库可能会有所帮助。


甚至更好的是,您可以使用 du -s /var | cut -f1 仅获取文件夹大小(以字节为单位)。 - Kim T

6

fast-folder-size使用Windows上的Sysinternals DU以及其他平台上内置的du程序,快速计算文件夹大小。

安装

npm i fast-folder-size

用法

const fastFolderSize = require('fast-folder-size')

fastFolderSize('.', (err, bytes) => {
  if (err) {
    throw err
  }

  console.log(bytes)
})

2
您应该尝试使用"getFolderSize"节点模块https://www.npmjs.com/package/get-folder-size
用法:
getFolderSize(folder, [regexIgnorePattern], callback)

例子:

var getSize = require('get-folder-size');

getSize(myFolder, function(err, size) {
  if (err) { throw err; }

  console.log(size + ' bytes');
  console.log((size / 1024 / 1024).toFixed(2) + ' Mb');
});

2
你好,请扩展你的答案,包括一个即使没有超链接也有用的解决方案。提前致谢。 - Chait
3
你所贴出的模块使用递归解决方案。https://github.com/alessioalex/get-folder-size/blob/master/index.js#L7 - Fomahaut
3
对于小型浅层目录来说可能还可以,但对于大型深层目录来说太糟糕了。我更愿意运行一个shell命令并让操作系统处理它。 它也没有提供磁盘上的实际占用空间大小 - 这是检查文件夹大小的常见动机。 - Radagast the Brown

0
以下函数递归扫描目录并返回其以字节为单位的大小。如果需要非阻塞方法,可以调整为使用fs/promises
const fs = require('fs');
const path = require('path');

const getDirSize = (dirPath) => {
  let size = 0;
  const files = fs.readdirSync(dirPath);

  for (let i = 0; i < files.length; i++) {
    const filePath = path.join(dirPath, files[i]);
    const stats = fs.statSync(filePath);

    if (stats.isFile()) {
      size += stats.size;
    } else if (stats.isDirectory()) {
      size += getDirSize(filePath);
    }
  }

  return size;
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接