如何分割一个巨大的文件夹?

我们在Windows上有一个文件夹,非常庞大。 我跑了“dir> list.txt”。 该命令在1.5小时后失去响应。 输出文件大约是200 MB。 它显示至less有280万个文件。 我知道情况很愚蠢,但让我们把重点放在这个问题上。 如果我有这样一个文件夹,我怎么能把它分割成一些“可pipe理”的子文件夹? 令人惊讶的是,我提出的所有解决scheme都涉及在某个时刻获取文件夹中的所有文件,这对我来说是不可能的。 有什么build议么?

感谢Keith Hill和Mehrdad。 我接受了Keith的回答,因为这正是我想要做的,但是我不能很快地让PS工作。

随着Mehrdad的提示,我写了这个小程序。 花费了7个多小时才能移动280万个文件。 所以最初的dir命令完成了。 但不知何故,它没有返回到控制台。

namespace SplitHugeFolder { class Program { static void Main(string[] args) { var destination = args[1]; if (!Directory.Exists(destination)) Directory.CreateDirectory(destination); var di = new DirectoryInfo(args[0]); var batchCount = int.Parse(args[2]); int currentBatch = 0; string targetFolder = GetNewSubfolder(destination); foreach (var fileInfo in di.EnumerateFiles()) { if (currentBatch == batchCount) { Console.WriteLine("New Batch..."); currentBatch = 0; targetFolder = GetNewSubfolder(destination); } var source = fileInfo.FullName; var target = Path.Combine(targetFolder, fileInfo.Name); File.Move(source, target); currentBatch++; } } private static string GetNewSubfolder(string parent) { string newFolder; do { newFolder = Path.Combine(parent, Path.GetRandomFileName()); } while (Directory.Exists(newFolder)); Directory.CreateDirectory(newFolder); return newFolder; } } } 

我使用Get-ChildItem将我的整个C:驱动器每晚编入c:\ filelist.txt。 这是大约580,000个文件,结果文件大小是〜60MB。 无可否认,我在Win7 x64上装有8 GB的RAM。 也就是说,你可能会尝试这样的事情:

 md c:\newdir Get-ChildItem C:\hugedir -r | Foreach -Begin {$i = $j = 0} -Process { if ($i++ % 100000 -eq 0) { $dest = "C:\newdir\dir$j" md $dest $j++ } Move-Item $_ $dest } 

关键是要以流媒体的方式进行。 也就是说,不要将所有Get-ChildItem结果收集到一个变量中,然后继续。 这将需要所有280万个FileInfos同时在内存中。 此外,如果您在Get-ChildItem上使用Name参数,它将输出包含文件路径相对于基本目录的单个字符串。 即使这样,也许这个大小只会压倒你可用的内存。 毫无疑问,执行需要相当长的一段时间。 IIRC正确,我的索引脚本需要几个小时。

如果确实起作用,你应该用c:\newdir\dir0 dir28直到dir28但是再次,我还没有测试过这个脚本,所以你的里程可能会有所不同。 顺便说一下,这种方法假定你是一个非常扁平的目标。

更新:使用Name参数差不多慢了一倍,所以不要使用该参数。

我发现GetChildItem是处理目录中的许多项目时最慢的选项。

看看结果:

 Measure-Command { Get-ChildItem C:\Windows -rec | Out-Null } TotalSeconds : 77,3730275 Measure-Command { listdir C:\Windows | Out-Null } TotalSeconds : 20,4077132 measure-command { cmd /c dir c:\windows /s /b | out-null } TotalSeconds : 13,8357157 

(用这样的listdir函数定义:

 function listdir($dir) { $dir [system.io.directory]::GetFiles($dir) foreach ($d in [system.io.directory]::GetDirectories($dir)) { listdir $d } } 

考虑到这一点,我会做什么:我会留在PowerShell,但使用更多的低级方法与.NET方法:

 function DoForFirst($directory, $max, $action) { function go($dir, $options) { foreach ($f in [system.io.Directory]::EnumerateFiles($dir)) { if ($options.Remaining -le 0) { return } & $action $f $options.Remaining-- } foreach ($d in [system.io.directory]::EnumerateDirectories($dir)) { if ($options.Remaining -le 0) { return } go $d $options } } go $directory (New-Object PsObject -Property @{Remaining=$max }) } doForFirst c:\windows 100 {write-host File: $args } # I use PsObject to avoid global variables and ref parameters. 

要使用代码,你必须切换到.NET 4.0运行时 – 枚举方法在.NET 4.0中是新的。

你可以指定任何scriptblock作为-action参数,所以在你的情况下,它会像{Move-item -literalPath $args -dest c:\dir }

试着列出前1000个项目,我希望它能很快完成:

 doForFirst c:\yourdirectory 1000 {write-host '.' -nonew } 

当然,你可以一次处理所有的项目,只是使用

 doForFirst c:\yourdirectory ([long]::MaxValue) {move-item ... } 

每件物品在退回后应立即处理。 所以整个列表不是一次读取然后处理,而是在阅读过程中处理。

从这开始如何:cmd / c dir / b> list.txt

这应该让你所有的文件名称的列表。

如果您在PowerShell提示符下执行“dir> list.txt”,则get-childitem将被视为“dir”。 Get-childitem已知枚举大型目录的问题,并且它返回的对象集合可能会变得很大。