我正在尝试编写一个脚本,该脚本将遍历文件夹中的 160 万个文件,并根据文件名将它们移动到正确的文件夹中。
原因是 NTFS 无法在不降低性能的情况下处理单个文件夹中的大量文件。
脚本调用“Get-ChildItem”来获取该文件夹中的所有项目,正如您所料,这会消耗大量内存(大约 3.8 GB)。
我很好奇是否有任何其他方法可以遍历目录中的所有文件而不会占用太多内存。
我正在尝试编写一个脚本,该脚本将遍历文件夹中的 160 万个文件,并根据文件名将它们移动到正确的文件夹中。
原因是 NTFS 无法在不降低性能的情况下处理单个文件夹中的大量文件。
脚本调用“Get-ChildItem”来获取该文件夹中的所有项目,正如您所料,这会消耗大量内存(大约 3.8 GB)。
我很好奇是否有任何其他方法可以遍历目录中的所有文件而不会占用太多内存。
如果你这样做
$files = Get-ChildItem $dirWithMillionsOfFiles
#Now, process with $files
您将面临记忆问题。
使用 PowerShell 管道处理文件:
Get-ChildItem $dirWithMillionsOfFiles | %{
#process here
}
第二种方式将消耗更少的内存,理想情况下不应超过某个点。
如果您需要减少内存占用,您可以跳过使用Get-ChildItem
,而直接使用 .NET API。我假设您使用的是 Powershell v2,如果是这样,请先按照此处的步骤启用 .NET 4 以在 Powershell v2 中加载。
在 .NET 4 中有一些很好的 API 用于枚举文件和目录,而不是在数组中返回它们。
[IO.Directory]::EnumerateFiles("C:\logs") |%{ <move file $_> }
通过使用这个API,而不是[IO.Directory]::GetFiles()
,一次只处理一个文件名,所以内存消耗应该是比较小的。
编辑
我还假设您尝试过一种简单的流水线方法,例如Get-ChildItem |ForEach { process }
. 如果这足够了,我同意这是要走的路。
但我想澄清一个常见的误解:在 v2 中Get-ChildItem
(或者实际上是 FileSystem 提供程序)并没有真正的流式传输。该实现使用 APIDirectory.GetDirectories
和Directory.GetFiles
,在您的情况下,它将在任何处理发生之前生成一个 1.6M 元素数组。完成此操作后,是的,管道的其余部分正在流式传输。是的,这个初始的低级部分的影响相对较小,因为它只是一个字符串数组,而不是丰富FileInfo
对象的数组。但是声称O(1)
在这种模式中使用了内存是不正确的。
相比之下,Powershell v3 是基于 .NET 4 构建的,因此利用了我上面提到的流 API(Directory.EnumerateDirectories
和Directory.EnumerateFiles
)。这是一个不错的更改,并且在像您这样的场景中有所帮助。
这就是我在不使用 .Net 4.0 的情况下实现它的方式。只有 Powershell 2.0 和老式的 DIR 命令:
这只是两行(简单)代码:
cd <source_path>
cmd /c "dir /B"| % { move-item $($_) -destination "<dest_folder>" }
我的 Powershell 进程仅使用 15MB。旧的 Windows 2008 服务器没有任何变化!
干杯!