我们在Windows上有一个文件夹......很大。我跑了“dir> list.txt”。该命令在1.5小时后丢失了响应。输出文件大约为200 MB。它显示至少有280万个文件。我知道情况很愚蠢,但让我们关注问题本身。如果我有这样的文件夹,我该如何将其拆分为一些“可管理”的子文件夹?令人惊讶的是,我提出的所有解决方案都涉及到在某个时刻获取文件夹中的所有文件,这在我的案例中是禁止的。有什么建议吗?
感谢Keith Hill和Mehrdad。我接受了基思的答案,因为这正是我想做的事情,但我不能让PS快速完成。
根据Mehrdad的提示,我写了这个小程序。移动280万个文件需要7个多小时。所以最初的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;
}
}
}
答案 0 :(得分:8)
我使用Get-ChildItem将我的整个C:驱动器每晚编入索引到c:\ filelist.txt。那是大约580,000个文件,结果文件大小约为60MB。不可否认,我使用的是带有8 GB RAM的Win7 x64。也就是说,您可以尝试这样的事情:
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
但最后,我还没有测试过这个脚本,所以你的里程可能会有所不同。顺便说一句,这种方法假设你是一个非常平坦的目录。
更新:使用Name
参数的速度几乎是其两倍,所以不要使用该参数。
答案 1 :(得分:2)
我发现在处理目录中的许多项目时,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中的新增功能。
您可以将任何脚本块指定为-action
参数,因此在您的情况下,它将类似于{Move-item -literalPath $args -dest c:\dir }
。
尝试列出前1000个项目,我希望它能很快完成:
doForFirst c:\yourdirectory 1000 {write-host '.' -nonew }
当然,您可以一次处理所有项目,只需使用
doForFirst c:\yourdirectory ([long]::MaxValue) {move-item ... }
并且每个项目在返回后应立即处理。因此,整个列表不会立即读取然后处理,而是在读取期间处理。
答案 2 :(得分:0)
从这开始怎么样: cmd / c dir / b> LIST.TXT
这应该会为您提供所有文件名的列表。
如果您在powershell提示符下执行“dir> list.txt”,则get-childitem的别名为“dir”。 Get-childitem已知存在枚举大型目录的问题,并且它返回的对象集合可能会变得很大。