PowerShell处理220万条CSV转JSON耗时过长,求优化方案
PowerShell处理220万条CSV数据的性能优化方案
问题描述
我正在使用PowerShell读取一个包含220万条记录的CSV文件,仅需处理第一列的数值型数据,将其转换为IP地址后写入JSON文件,但该操作耗时近12小时,恳请提供代码优化方案或多线程实现思路。
示例CSV仅关注第一列,第一列内容为整数格式的IP地址(如3232235521对应192.168.0.1)。
现有代码
function Read-IPData { $dbFilePath = Get-ChildItem -Path $rootDir -Filter "IP2*.CSV" | ForEach-Object{ $_.FullName } Write-Host "file path - $dbFilePath" Write-Host "Reading..." $data = Get-Content -Path $dbFilePath | Select-Object -Skip 1 Write-Host "Reading data finished" $count = $data.Count Write-host "Total $count records found" return $data } function Convert-NumbetToIP { param( [Parameter(Mandatory=$true)][string]$number ) try { $w = [int64]($number/16777216)%256 $x = [int64]($number/65536)%256 $y = [int64]($number/256)%256 $z = [int64]$number%256 $ipAddress = "$w.$x.$y.$z" Write-Host "IP Address - $ipAddress" return $ipAddress } catch { Write-Host "$_" continue } } Write-Host "Getting IP Addresses from $dbFileName" $data = Read-IPData Write-Host "Checking whether output.json file exist, if not create" $outputFile = Join-Path -Path $rootDir -ChildPath "output.json" if(!(Test-Path $outputFile)) { Write-Host "$outputFile does not exist, creating..." New-Item -Path $outputFile -type "file" } foreach($item in $data) { $row = $item -split "," $ipNumber = $row[0].trim('"') Write-Host "Converting $ipNumber to ipaddress" $toIpAddress = Convert-NumbetToIP -number $ipNumber Write-Host "Preparing document JSON" $object = [PSCustomObject]@{ "ip-address" = $toIpAddress "is-vpn" = "true" "@timestamp" = (Get-Date).ToString("o") } $document = $object | ConvertTo-Json -Compress -Depth 100 Write-Host "Adding document - $document" Add-Content -Path $outputFile $document }
性能瓶颈分析
现有代码的主要性能问题包括:
- 全量加载数据:
Get-Content一次性将220万行加载到内存,占用大量资源且加载速度慢 - 逐行写入文件:
Add-Content每次写入都要执行打开、写入、关闭文件操作,IO开销极大 - 大量控制台输出:
Write-Host的控制台输出是高耗时操作,220万次调用会严重拖慢整体速度 - 低效的字符串处理:手动用
-split ","分割CSV行,比专用CSV解析器效率低 - 重复的JSON序列化:每次循环调用
ConvertTo-Json,单条数据的序列化开销累积后非常可观 - IP转换的计算冗余:手动计算IP段的方式可替换为更高效的原生.NET方法
优化方案
一、单线程优化(立竿见影,兼容PowerShell 5.1/7)
以下优化无需并行即可大幅降低耗时:
用
Import-Csv高效读取指定列
避免手动分割CSV,直接用Import-Csv加载仅需要的第一列,减少内存占用和解析开销。批量写入文件
先将所有JSON字符串收集到数组中,最后一次性写入文件,避免频繁IO操作。移除冗余控制台输出
完全移除所有Write-Host,或仅保留关键进度提示(如每10万条输出一次)。优化IP转换逻辑
使用.NET原生的[System.Net.IPAddress]类进行转换,比手动计算更高效可靠。手动构造JSON字符串
由于JSON结构固定,直接拼接字符串替代ConvertTo-Json,减少序列化开销。
单线程优化后代码
$rootDir = "你的CSV文件所在目录" $outputFile = Join-Path -Path $rootDir -ChildPath "output.json" # 获取CSV文件路径 $dbFilePath = (Get-ChildItem -Path $rootDir -Filter "IP2*.CSV").FullName if (-not $dbFilePath) { Write-Error "未找到目标CSV文件" exit 1 } # 定义高效IP转换函数 function Convert-NumberToIP { param([Parameter(Mandatory=$true)][int64]$number) try { $bytes = [System.BitConverter]::GetBytes($number) [array]::Reverse($bytes) # 转换为大端字节序 return [System.Net.IPAddress]::new($bytes).ToString() } catch { # 异常处理,可记录错误到日志,此处跳过错误记录 return $null } } # 初始化结果数组 $jsonLines = @() $recordCount = 0 $timestamp = (Get-Date).ToString("o") # 若允许统一时间戳,用此;若需每条实时,移到循环内 # 逐行读取CSV(自动跳过表头) Import-Csv -Path $dbFilePath -Header "IPNumber", "Ignore" | ForEach-Object { $recordCount++ $ipNumber = [int64]$_.IPNumber $ipAddress = Convert-NumberToIP -number $ipNumber if ($ipAddress) { # 手动构造JSON字符串 $jsonLine = @" {"ip-address":"$ipAddress","is-vpn":"true","@timestamp":"$timestamp"} "@ $jsonLines += $jsonLine } # 可选:每10万条输出一次进度,避免控制台拖慢 if ($recordCount % 100000 -eq 0) { Write-Host "已处理 $recordCount 条记录" } } # 一次性写入所有内容 Set-Content -Path $outputFile -Value $jsonLines -Encoding utf8 Write-Host "处理完成,共写入 $($jsonLines.Count) 条记录到 $outputFile"
二、并行处理优化(PowerShell 7+推荐,进一步提升速度)
如果使用PowerShell 7及以上版本,可利用ForEach-Object -Parallel实现并行处理,充分利用多核CPU。注意文件写入的线程安全,建议每个线程处理一块数据并写入临时文件,最后合并临时文件。
并行处理示例代码
$rootDir = "你的CSV文件所在目录" $outputFile = Join-Path -Path $rootDir -ChildPath "output.json" $tempDir = Join-Path -Path $rootDir -ChildPath "temp_parallel" $batchSize = 50000 # 每批处理5万条记录 # 创建临时目录 if (-not (Test-Path $tempDir)) { New-Item -Path $tempDir -ItemType Directory | Out-Null } # 获取CSV文件路径 $dbFilePath = (Get-ChildItem -Path $rootDir -Filter "IP2*.CSV").FullName if (-not $dbFilePath) { Write-Error "未找到目标CSV文件" exit 1 } # 定义并行处理用的IP转换函数 $convertIpScriptBlock = { param([int64]$number) try { $bytes = [System.BitConverter]::GetBytes($number) [array]::Reverse($bytes) return [System.Net.IPAddress]::new($bytes).ToString() } catch { return $null } } # 读取所有数据并分块 $allData = Import-Csv -Path $dbFilePath -Header "IPNumber", "Ignore" $batches = for ($i=0; $i -lt $allData.Count; $i+=$batchSize) { $allData[$i..($i+$batchSize-1)] | Where-Object { $_ } } # 并行处理每批数据 $batchIndex = 0 $batches | ForEach-Object -Parallel { $batch = $_ $batchNum = $using:batchIndex++ $tempFile = Join-Path -Path $using:tempDir -ChildPath "batch_$batchNum.json" $convertFunc = $using:convertIpScriptBlock $timestamp = (Get-Date).ToString("o") $jsonLines = @() foreach ($item in $batch) { $ipNumber = [int64]$item.IPNumber $ipAddress = & $convertFunc -number $ipNumber if ($ipAddress) { $jsonLines += @" {"ip-address":"$ipAddress","is-vpn":"true","@timestamp":"$timestamp"} "@ } } Set-Content -Path $tempFile -Value $jsonLines -Encoding utf8 Write-Host "批次 $batchNum 处理完成" } -ThrottleLimit 8 # 按需设置并发数,建议等于CPU核心数 # 合并所有临时文件到最终输出 Get-ChildItem -Path $tempDir -Filter "batch_*.json" | Sort-Object Name | Get-Content | Set-Content -Path $outputFile -Encoding utf8 # 清理临时文件 Remove-Item -Path $tempDir -Recurse -Force Write-Host "并行处理完成,结果已写入 $outputFile"
优化效果说明
- 单线程优化后,耗时可降低至几十分钟甚至更短(取决于硬件)
- 并行处理可进一步利用多核CPU,在8核机器上可将耗时压缩至十几分钟
内容的提问来源于stack exchange,提问作者Naveen Kumar
相关产品推荐
相关产品推荐

