Powershell:替换csv文件中的字符串会导致“错误”;类型'的例外情况;System.OutOfMemoryException';被扔出去了。”;
我正在编写一个简单的脚本(如我所想)来替换CSV文件中的一些字符串。这些字符串被称为对象的“键”。我基本上用“新密钥”替换文件中的“旧密钥” 我遇到的问题是:当脚本处理第一个文件(外部循环的第一次迭代)时,我得到: 此错误引用了替换代码行:Powershell:替换csv文件中的字符串会导致“错误”;类型'的例外情况;System.OutOfMemoryException';被扔出去了。”;,powershell,exception,memory,Powershell,Exception,Memory,我正在编写一个简单的脚本(如我所想)来替换CSV文件中的一些字符串。这些字符串被称为对象的“键”。我基本上用“新密钥”替换文件中的“旧密钥” 我遇到的问题是:当脚本处理第一个文件(外部循环的第一次迭代)时,我得到: 此错误引用了替换代码行: $txtsourceFile = $txtsourceFile -replace $findReplaceitem.$OldIssueKey , $findReplaceitem.$IssueKey csv文件“很大”,但实际上没有那么大。 映射列表为1
$txtsourceFile = $txtsourceFile -replace $findReplaceitem.$OldIssueKey , $findReplaceitem.$IssueKey
csv文件“很大”,但实际上没有那么大。映射列表为1.7 MB 每个源文件大约有1.5 MB 我真的不明白我是如何遇到这些文件大小的内存问题的。和ofc。我不知道如何避免那个问题 我发现一些博客讨论PS中的内存问题。他们最终都更改了PowerShell MaxMemoryPerShellMB配额默认值。这对我来说根本不起作用,因为我遇到了一个错误
get-item WSMAN:\localhost\shell\MaxMemoryPerShellMB
说“获取项:找不到路径'WSMan:\localhost\Shell\MaxMemorPerShellMB',因为它不存在。”
我在VS代码中工作。正如@BACON所暗示的,这里的核心问题是通过(可能)数千个替换循环引起的 每次执行替换行时:
$txtsourceFile = $txtsourceFile -replace $findReplaceitem.$OldIssueKey , $findReplaceitem.$IssueKey
PowerShell首先有一块内存用于$txtsourceFile
。它分配一个新的内存块来存储文本替换后的数据副本
这通常是“ok”,因为您将有一个包含替换文本的有效内存块,以及一个包含原始文本的“无效”副本。由于大多数人都有(相对)大量内存,我们通常可以通过在后台定期运行垃圾收集器来“清理”这些无效数据来处理.NET中的“泄漏”
我们遇到的麻烦是,当我们快速循环数千次时,我们也会快速生成数千份数据副本。在垃圾收集器有机会运行和清理数千个无效数据拷贝(即3.2GB)之前,您的可用可用内存最终耗尽。见:
有几种方法可以解决此问题:
解决方案1:大而慢的方法和低效的方法
如果需要处理整个文件(即跨换行符),可以使用相同的代码,并在执行期间定期手动运行垃圾收集器,以“更好地”管理内存:
这有两件事:
$count
200)[GC]::Collect()
但是根据这一点,当试图将集合强制放入循环中时,它并不总是起作用。使用:
[System.GC]::GetTotalMemory('forceFullCollection')
完全停止执行,直到垃圾收集完成后再继续
解决方案2:更快、更节省内存的方法,一次一行
如果可以一次一行执行所有替换,那么可以使用来流式处理文件,一次处理一行并写入它
try
{
$SR = New-Object -TypeName System.IO.StreamReader -ArgumentList $sourceFile.FullName
$SW = [System.IO.StreamWriter] $outputFullFileName
while ($line = $SR.ReadLine()) {
#Loop through Replacements
ForEach ($findReplaceItem in $findReplaceList) {
$Output = $line -replace $findReplaceitem.$OldIssueKey, $findReplaceitem.$IssueKey
}
$SW.WriteLine($output)
}
$SR.Close() | Out-Null
$SW.Close() | Out-Null
}
finally
{
#Cleanup
if ($SR -ne $null)
{
$SR.dispose()
}
if ($SW -ne $null)
{
$SW.dispose()
}
}
这应该运行快一个数量级,因为您将一次工作一行,并且不会在每次替换时创建整个文件的数千个副本。我发现上面的答案和注释非常有用,并在此处实现了一个接近答案的解决方案: 我将$findReplaceList分为多个批次(大约37000个条目长,我开始将其分为1000个),并使用GC进行批处理。 现在,我可以看到内存使用率在批处理过程中上升,而在批处理完成后再次下降 于是我发现了一个有趣的行为:内存问题仍然在几批中出现。。。因此,我进一步分析了findReplaceList,得出以下结果: 在某些情况下,文件中没有$OldIssueKey
PS是否会将其视为一个空字符串并尝试替换所有这些字符串?简短更新:如果我在执行过程中检查系统内存消耗:Process Windows Powershell在异常情况下停止之前会占用高达3.2 GB的内存。
$mappingList
文件中可能有多少个问题密钥?对于一个给定的$sourceFile
,有多少键可以被重新映射?尽管这两个文件都小于2MB,但每次引用的错误行导致更改时,它都会生成一个稍有不同但仍然是全新的对象,表示完整的源文件。如果定义了10000个映射,并且在源文件中找到了1000个映射,那么要收集的垃圾量为1000×1.7MB=1.7GB。如果映射较短但数量较多,则数学会变得更糟。@BACON的建议与我的想法相同,但我对PowerShell中的gc了解不够。你确定那只是副本吗?错误消息中也存在同样的拼写错误?此外,当您说要替换“键”时,是重新映射整个列(单元格)值,还是任意搜索文本可能是值的子字符串(如亵渎过滤器)?这可以使用[Hashtable]
/[Dictionary]
逐行处理以执行映射,这将大大减少运行时和内存使用,但这需要替换整个值。我发现上面的答案和注释非常有用,并在这里实现了一个接近答案的解决方案:我将$findReplaceList拆分为多个批次(大约37000个条目,我开始拆分为1000个)然后用GC进行批处理。这产生了一个非常有趣的结果!对空字符串进行匹配:“abc”-replace”,“z”
返回zazbzcz
看起来它匹配每个字符(包括下线字符),并用替换文本加上现有字符替换它。所以如果你有一个拥抱
[GC]::Collect()
[System.GC]::GetTotalMemory('forceFullCollection')
try
{
$SR = New-Object -TypeName System.IO.StreamReader -ArgumentList $sourceFile.FullName
$SW = [System.IO.StreamWriter] $outputFullFileName
while ($line = $SR.ReadLine()) {
#Loop through Replacements
ForEach ($findReplaceItem in $findReplaceList) {
$Output = $line -replace $findReplaceitem.$OldIssueKey, $findReplaceitem.$IssueKey
}
$SW.WriteLine($output)
}
$SR.Close() | Out-Null
$SW.Close() | Out-Null
}
finally
{
#Cleanup
if ($SR -ne $null)
{
$SR.dispose()
}
if ($SW -ne $null)
{
$SW.dispose()
}
}