如何在不耗尽内存的情况下删除Powershell中的重复项?

如何在不耗尽内存的情况下删除Powershell中的重复项?,powershell,Powershell,我目前正在Windows Powershell中使用此命令从简单的1行CSV中删除重复项 gc combine.csv | sort | get-unique > tags.cs 每当我在150mb CSV(2000万行猜测)上运行它时,任务管理器都会显示Powershell正在消耗所有可用内存(32GB),然后使用虚拟内存。我还让脚本运行了大约一个小时,但没有完成。我觉得这很奇怪,因为在excel中,从我的1M行CSV中删除重复项通常需要几秒钟。关于如何处理这个问题,有什么建议吗?Ex

我目前正在Windows Powershell中使用此命令从简单的1行CSV中删除重复项

gc combine.csv | sort | get-unique > tags.cs

每当我在150mb CSV(2000万行猜测)上运行它时,任务管理器都会显示Powershell正在消耗所有可用内存(32GB),然后使用虚拟内存。我还让脚本运行了大约一个小时,但没有完成。我觉得这很奇怪,因为在excel中,从我的1M行CSV中删除重复项通常需要几秒钟。关于如何处理这个问题,有什么建议吗?

Excel的设计是为了高效地处理这么大的文件(显然?我有点惊讶)

代码的主要问题是您正在对其进行排序。我知道您这样做是因为
Get Unique
需要它,但是
Sort Object
的工作方式是,它需要收集内存中发送到它的每个项目(在本例中是文件的每一行),以便实际进行排序。与您的文件不同,它不仅将其存储为平面内存,还将其存储为N个字符串,其中N是文件中的行数,以及内存字符串中的所有开销。正如Tessellingheckler指出的那样,它似乎更多地与排序联系在一起,而不是与存储联系在一起

您可能希望在处理给定行时确定该行是否唯一,以便可以立即丢弃它

为此,我推荐几套。特别是一个,或者,如果你真的需要它排序,一个

代码的简单转换:

Get-Content combine.csv | 
    ForEach-Object -Begin { 
        $h = [System.Collections.Generic.HashSet[String]]::new() 
    } -Process { 
        if ($h.Add($_)) {
            $_
        }
    } |
    Set-Content tags.cs
对我来说,在一个大于650MB的文件上测试它,大约4M行,其中只有26行是唯一的,只花了一分钟多的时间,并没有明显影响RAM

大约一半行是唯一的同一个文件大约需要2分钟,并且使用了大约2GB的RAM(使用
SortedSet
它需要2.5分钟多一点,大约2.4GB)

同样的后一个文件,即使从
|sort | gu
简化为
|sort-Unique
,在大约10秒内使用了超过5 GB的RAM

如果您开始使用
StreamReader.ReadLine
for
循环以及其他一些东西,您可能会获得更高的性能,但我将留给您一个练习

似乎在大多数实现中,在最好的情况下,所使用的RAM量在很大程度上取决于有多少项是唯一的(更多的唯一项意味着更多的RAM)。

您可以尝试:

Get-Content combine.csv -ReadCount 1000 | 
    foreach-object { $_ } | 
    Sort-Object -Unique | 
    Set-Content tags.cs
gc combine.csv-read 1kb |%{$| | sort-uniq | sc tags.cs

但我想你也会遇到同样的问题。如果您想要更快的结果,并且不需要对其进行排序,则只需要无重复:

$Lines = [System.Collections.Generic.HashSet[string]]::new()


$Lines.UnionWith([string[]][System.IO.File]::ReadAllLines('c:\path\to\combine.csv'))


[System.IO.File]::WriteAllLines('c:\path\to\tags.cs', $Lines)

它在23秒和~1.5GB内存中运行在我的测试20M随机数文件上。如果确实需要对它们进行排序,请使用
SortedSet
而不是
HashSet
,它在5分钟内运行,Get Content和stdio
都非常慢。Net可能会给您带来更好的性能

尝试:

在我自己的带有4列1000000行csv的盒子上测试时,我在22秒时达到650MB的内存利用率。使用get content和
运行相同的csv需要2GB内存和60秒

通过对此处类似问题()的一些附加技巧,您可以通过将数据强制转换为哈希集以获得唯一值,然后转换为列表并运行排序方法来进一步缩短时间,因为这似乎比PowerShell的排序对象快一点

$stream = [System.IO.StreamWriter] "tags.csv"
$UniqueItems = [system.collections.generic.list[string]]([System.Collections.Generic.HashSet[string]]([System.IO.File]::ReadLines("combine.csv")))
$UniqueItems.sort()
$UniqueItems | % { $Stream.writeline($_) }
$Stream.close()

在我的同一个数据集上使用它,我可以在1秒钟内完成,内存使用量为144MB。

冒着声明明显错误的风险。。。使用Excel。这将是一个更好的选择,但是Excel有1.2M的行限制。对于那些好奇的人来说,去看看为什么它使用这么多RAM并提交修复;)--@TessellatingHeckler非常有趣的是,
sort-unique
在与部分结果一起使用时使用
SortedSet
-Top
-Bottom
)。在那里有一条评论说,它可能也值得用于
-Unique
。在讨论了PowerShell的空闲时间后,
Get Content
正在做的是将
NoteProperties
添加到文件中的每一行,包括
PSChildName、PSDrive、PSParentPath、PSProvider、,读取计数
额外数据。其中2个是
[PSDriveInfo]
[ProviderInfo]
对象,而不是字符串。2000万次,这个数字可以解释很多记忆
-ReadCount
可以通过一次读取多行代码来帮助减少这种情况,但需要额外的处理才能将其拆分。(@briantist)刚刚在
readLines
vs
readAllLines
上发现:readLines和readAllLines方法的区别如下:使用readLines时,可以在返回整个集合之前开始枚举字符串集合;使用ReadAllLines时,必须等待返回整个字符串数组,然后才能访问该数组。因此,当您处理非常大的文件时,读取行可以更高效。@tiberriver256哦,真有趣!我假设
readlines
适用于某些行,但不是所有行。是,它返回
IEnumerable
ReadAllLines
返回
string[]
。而
UnionWith
也可以与IEnumerable一起使用。遗憾的是,我刚刚删除了我的测试文件,所以我无法对它进行类似的测试。我很想看看您对将哈希集转换为列表,然后调用排序方法与SortedSet的比较。在我的测试中,列表和排序比排序集快得多。@tiberriver256这是个好主意;新的测试文件,2000万行随机数,约19.9万个唯一数和约10万个重复数
SortedSet
UnionWith
->317秒,大约是我之前得到的。我刚刚注意到SortedSet可以从IEnumerable初始化,所以
[Collections.Generic.SortedSet[string]]::new([IO.File]::ReadLines($File)
$stream = [System.IO.StreamWriter] "tags.csv"
$UniqueItems = [system.collections.generic.list[string]]([System.Collections.Generic.HashSet[string]]([System.IO.File]::ReadLines("combine.csv")))
$UniqueItems.sort()
$UniqueItems | % { $Stream.writeline($_) }
$Stream.close()