目录的总文件大小相差很大:Ruby -e, du -ach, ls -al "total"

Summing total file sizes of directory is different by a large margin: Ruby -e, du -ach, ls -al "total"

ls | ruby -ne 'BEGIN{a= []}; a <<  File.size($_.chomp).to_i; END{puts a.sum}'

上面的代码获取每个文件的文件大小,将其放入数组中,并打印总和。

返回的值非常不同于:

du -ach

这两个值都与显示的总计有很大不同:

ls -al

没有隐藏文件。

MacOs

如果 du 向您展示了很多 4K 和 8K 文件,这是因为它向您展示了 block size。为了性能,磁盘上的存储由块组成。现在一个典型的块是 4K。即使是一个字节也会占用一个完整的块。

$ echo '1' > this

$ hexdump this
0000000 31 0a                                          
0000002

$ ls -l this
-rw-r--r-- 1 schwern staff 2 Dec  5 15:16 this

$ du -h this
4.0K    this

$ du --apparent-size -h this
2   this

$ ruby -e 'puts File.size(ARGV[0])' this
2

相关文件有 2 个字节的内容。 ls -lFile.size 报告两个字节的内容。

du,默认情况下,报告文件的块大小。这是因为它是一个磁盘使用情况工具,而您想知道实际占用的磁盘数量。这 2 个字节占用 4K 磁盘空间。 1000 个 2 字节文件将占用 4000K,而不是 2000 字节。

出于这个原因,许多程序会避免拥有许多小文件,而是通过将它们打包成单个 image file. A simple example is Git packfiles.

来节省磁盘空间 space

问题是你如何定义“大小”,你如何定义“总和”,你是否 100% 确定你展示的所有三个例子实际上测量的是同一件事(即所有三个都定义这两个术语完全相同)?

这里只是一些需要考虑的例子。

稀疏文件

稀疏文件 是许多文件系统的一个特性,它优化了包含长 运行 二进制零的文件的存储。该文件实际上并没有 存储 零,而是仅包含文件中存在“漏洞”的信息,并且在读取文件时,OS 将 return 零,即使它们没有物理存储在文件中。

最极端的示例是包含 个零的文件。我可以在几个字节中存储信息“此文件包含 2 TB 的零”,但是,当我要求操作系统打开并读取文件时,我将“看到”2 TB 的零。现在,这个文件的“大小”是多少?它是 2TB 还是实际上只需要几个字节来编码稀疏文件的“漏洞”信息(在本例中覆盖整个文件)?

我曾经通过在 1.44MB 软盘(或最近的 32GB U 盘)上创建 TB 大小的稀疏文件来迷惑我的朋友。

元数据开销

文件系统不仅要存储文件的内容,还要存储某种元数据关于文件:文件是什么时候创建的,文件最后一次修改是什么时候、上次访问文件的时间、文件的所有者等。

此元数据也占用 space。你算不算?请注意,每个文件系统都不同!

块大小

许多文件系统都有一个可能的最小分配大小,称为“块”。分配 space 小于块是不可能的,所以除非文件大小是块大小、文件内容大小和磁盘文件大小的整数倍永远不同。

这对于非常小的文件和非常大的块大小尤其明显。例如。仅包含以 ASCII 编码的字符串“Hello”的文件最多包含 7 个字节(最坏情况假设它以换行符结尾,并且换行符是 Windows 样式的 CRLF),但它会占用磁盘上的整个块(通常为 4KB)。

元数据内联

另一方面,在某些文件系统上,非常小的文件会内联到它们的元数据条目中。因此,它们根本不需要任何 data 块。这是否意味着它们的大小为 0?

尾部共享

在某些文件系统上,多个文件的“尾部”可以共享一个块。因此,如果您有多个文件,其大小不是块大小的整数倍,则不是为每个文件的每个“尾端”分配一个大部分为空的块,而是将多个文件的“尾端”填充到一个块中.

但是,现在这个块属于多个文件,所以如果你孤立地询问每个文件的大小,这个块会被报告多次。

同一文件的多个条目

许多文件系统将“文件”的概念与“文件名”的概念分开。例如,在 Unix 以及任何派生或受其启发的系统中(Linux、macOS、Android、...),“文件”只是一个未命名的数据块。 目录 是一种特殊类型的文件,它将 namesfiles.

相关联

但是,这意味着一个文件可以有多个名称!那么,如果你的目录中有同一个文件,但有两个不同的名字,那么你算一次还是两次?

目录条目内联

类似于元数据内联,如果文件很小,而且文件只有一个名字,那么我们可以把文件的数据放到目录项中,而不是在目录项中放一个指向文件的指针直接进入目录。

同样,如果我们在查看文件大小时忽略目录条目,则该文件在磁盘上的大小似乎为 0。

去重

一些文件系统执行重复数据删除,它们试图找到具有相同内容的块,然后透明地将这两个块替换为 link 到一个块。

现在,当两个完全不相关的文件碰巧在其中某处有 运行 相同的内容,因此共享一些已删除重复的块时,您是计算这些块一次还是两次?

压缩

一些文件系统透明地压缩文件的内容。这意味着磁盘上文件的实际大小取决于文件内容的可压缩性。

那么,你算压缩后的大小还是未压缩的大小?

备用数据流/分叉

一些文件系统具有允许您在单个文件中存储多个数据流的功能。例如,NTFS 允许您在文件中存储所谓的“备用数据流”。应用程序使用它来存储额外的特定于应用程序的元数据,例如音乐播放器用它来存储音乐文件中的专辑封面,或计算歌曲播放的频率,或特定于歌曲的均衡器设置等,办公应用程序用它来存储文件旧版本的备份,等等。 MacOS 有一个类似的功能叫做“Forks”。

几乎所有标准文件系统 API 都将只提供默认流/数据分叉。除非您使用通常 OS 特定或文件系统特定的 API 明确要求备用数据流/资源叉,否则您永远不会知道它在那里,但它可能非常大。

“捆绑包”

特别是在 macOS 上,您有“捆绑包”的概念,就文件系统和 OS 的较低级别而言,它在技术上是目录,但主要被视为呈现给更高级别的 OS 和用户时的单个文件。

所以,这里你有一个看起来像文件的东西,你认为“它的大小应该很容易确定”,但它实际上是一个目录,包含你在你的文件中注意到的所有问题问题。

以上任意组合

当然,以上所有内容都可以相互组合。

因此,如您所见,当您计算多个文件的大小总和时,这并不是一件简单的事情。文件可以共享数据。

但是即使你忘了总和,只问单个文件的大小,答案仍然不清楚,因为有很多不同的方式来定义什么“大小”是指。

因此,为了对问题有一个有意义的答案,您需要实际退后几步,问问自己:

  1. 为什么要测量目录文件大小的总和?你需要这些信息做什么?你的最终目标是什么?您实际上将根据这些信息做出哪些决定?您将如何使用这些信息?

  2. 什么您实际上需要进行衡量以获得决策所依据的必要信息?

  3. 你是如何测量这个的?根据您对问题 #2 的回答,您需要的信息可能非常 OS 特定或文件系统特定,以及您甚至无法作为用户访问的内部文件系统 API 的一部分。