如何逆序执行Get-ChildItem并优化超50万文件的遍历脚本?
针对大文件夹遍历与冗余文件删除的优化方案
首先解决你最关心的逆序遍历需求,然后再从根本上优化脚本性能——毕竟50万+文件的场景,原脚本的写法确实会导致极端缓慢的执行速度。
一、实现Get-ChildItem逆序遍历
针对大文件夹,推荐两种逆序方式,优先选第一种流式处理的方案(内存更友好):
方案1:使用.NET API流式逆序
Get-ChildItem本质是封装了.NET的DirectoryInfo,直接用[System.IO.Directory]::EnumerateFiles()可以实现流式遍历(不一次性加载所有文件到内存),再通过反转数组实现逆序:
# 流式获取文件路径,逆序遍历 $files = [System.IO.Directory]::EnumerateFiles($RPT) [Array]::Reverse($files) foreach ($filePath in $files) { $file = Get-Item $filePath # 后续逻辑... }
方案2:Sort-Object配合Get-ChildItem
如果想更简洁,也可以用Sort-Object,但注意大文件夹下Sort-Object需要先加载所有文件到内存,可能占用较多内存:
Get-ChildItem -Path $RPT -File | Sort-Object -Property Name -Descending | ForEach-Object { # 后续逻辑... }
二、脚本性能核心优化(从24小时→数小时的关键)
原脚本的性能瓶颈主要在重复正则编译、低效集合查询、冗余操作这几个点,我们逐一优化:
1. 将数组查询改为哈希表(O(1)查询替代O(n)遍历)
原脚本中$submidlist -match "^$number$"和$jobidlist -match "^$number$"会遍历整个数组判断匹配,50万+循环下来会产生天文数字的遍历次数。改成哈希表查询速度会提升几个数量级:
# 提前将列表转为哈希表,初始化一次即可 $submidHash = @{} $submidlist | ForEach-Object { $submidHash[[string]$_] = $true } $jobidHash = @{} $jobidlist | ForEach-Object { $jobidHash[[string]$_] = $true }
后续判断直接用:
if ($submidHash.ContainsKey($number)) { ... } if ($jobidHash.ContainsKey($number)) { ... }
2. 预编译正则表达式,避免重复编译
原脚本每次循环都重新解析正则字符串,预编译后复用可以大幅减少CPU消耗:
# 预编译要保留的文件后缀/名称正则 $keepPattern = [regex]::new('acct\.csv|jpd\.csv|jss\.pdf|jman\.pdf|3600\.pdf|cont\.pdf|msl\.txt|pres\.pdf|tray\.pdf|qual\.pdf|zipl\.pdf') # 预编译提取12位数字的正则 $numberPattern = [regex]::new('\d{12}')
后续判断用:
$isKeepFile = $keepPattern.IsMatch($_.Name) $match = $numberPattern.Match($_.Name)
3. 减少不必要的输出,降低IO开销
原脚本每次删除都用echo输出,大量的控制台IO会拖慢速度。可以改成批量输出或者用进度条替代:
# 每处理1000个文件更新一次进度条 if ($totalcount % 1000 -eq 0) { Write-Progress -Activity "清理冗余文件" -Status "已处理 $totalcount 个文件,已删除 $count 个" -PercentComplete ([math]::Min(($totalcount / 500000) * 100, 100)) }
4. 最终优化后的完整脚本示例
# 初始化参数 $RPT = "你的文件夹路径" $submidlist = @("你的submid列表") $jobidlist = @("你的jobid列表") $totalcount = 0 $count = 0 # 预编译正则和哈希表 $numberPattern = [regex]::new('\d{12}') $keepPattern = [regex]::new('acct\.csv|jpd\.csv|jss\.pdf|jman\.pdf|3600\.pdf|cont\.pdf|msl\.txt|pres\.pdf|tray\.pdf|qual\.pdf|zipl\.pdf') $submidHash = @{} $submidlist | ForEach-Object { $submidHash[[string]$_] = $true } $jobidHash = @{} $jobidlist | ForEach-Object { $jobidHash[[string]$_] = $true } # 逆序流式遍历文件 $files = [System.IO.Directory]::EnumerateFiles($RPT) [Array]::Reverse($files) foreach ($filePath in $files) { $file = Get-Item $filePath $totalcount++ # 提取12位数字 $match = $numberPattern.Match($file.Name) if (-not $match.Success) { continue } $number = [int]$match.Value $isKeepFile = $keepPattern.IsMatch($file.Name) # 判断是否删除 $flag = 0 if ($submidHash.ContainsKey($number) -and -not $isKeepFile) { Write-Host "DELETE SUBMID $($file.Name)" Remove-Item -Path $filePath -Force -ErrorAction SilentlyContinue $count++ $flag = 1 } if ($jobidHash.ContainsKey($number) -and $isKeepFile) { Write-Host "DELETE JOBID $($file.Name)" Remove-Item -Path $filePath -Force -ErrorAction SilentlyContinue $count++ $flag = 1 } # 每1000次更新进度 if ($totalcount % 1000 -eq 0) { Write-Progress -Activity "清理冗余文件" -Status "已处理 $totalcount 个文件,已删除 $count 个" -PercentComplete ([math]::Min(($totalcount / 500000) * 100, 100)) } } Write-Progress -Activity "清理冗余文件" -Completed Write-Host "清理完成,共处理 $totalcount 个文件,删除 $count 个"
三、额外建议
- 如果服务器CPU核心足够,可以尝试用
ForEach-Object -Parallel并行处理,但注意文件操作的线程安全,建议先小范围测试; - 定期清理文件夹,避免文件数量积累到50万+,可以考虑按日期归档旧文件;
- 用
Measure-Command测试脚本的执行时间,对比优化前后的差异,找到进一步优化的空间。
内容的提问来源于stack exchange,提问作者fudge
相关产品推荐
相关产品推荐

