通过比较两个对象来构建对象的代码以提升性能
我需要在PowerShell中更快地把一个对象从另一个对象构建出来。
我有一个包含10,000个主机名的PowerShell字符串数组:
$hostnames = @("server1","server2","server3"..."server10000")
还有一个包含自定义对象的PowerShell数组,其NoteProperty为 "hostname" 和 "IP"。两个Note Property都是字符串。该数组共有50,000个元素。
$inventory[0].name="server1"
$inventory[0].ip="10.0.0.1"
$inventory[1].name="server2"
$inventory[1].ip="10.0.0.2"
我需要基于 $inventory中的元素,且仅针对与 $hostnames匹配的那些元素,构建一个新对象。我写了这段代码:
$newList = @()
foreach($name in $hostnames){
$newList += $inventory | Where-Object {$_.name -eq $name}
}
这段代码最终能跑通。當 $hostnames中有一万個元素时,它必须遍历 $inventory的 50,000个元素一万次。这需要花费很长时间。
我该如何优化这段代码?是否应该使用.Net类来替代PowerShell的数组?除了带Where-Object的 foreach循环之外,是否还有其他算法?
解决方案
在筛选大型集合时,为了获得最佳性能,你应该避免线性比较,目前你是在对每个 $hostnames 项对 $inventory 集合进行完全循环。相反,使用基于哈希的查找,对你的 $hostnames 使用一个 Hashset<string> 的哈希集合,然后使用一个经典的 foreach 配合 if 条件进行过滤。你还应该避免在数组上进行 +=,相反从循环中输出并把循环表达式赋值给一个变量,参见 array addition。
下面的代码看起来是这样的:
$hostnames = [System.Collections.Generic.HashSet[string]]::new(
[string[]] @('server1', 'server2', 'server3', '...', 'server10000'),
[System.StringComparer]::OrdinalIgnoreCase)
$inventory = @(
@{ name = 'server1'; ip = '10.0.0.1' }
@{ name = 'server2'; ip = '10.0.0.2' }
@{ name = 'notinhostnames'; ip = '10.0.0.2' })
$invtoryObjectsInHostnames = foreach ($object in $inventory) {
if ($hostnames.Contains($object.name)) {
$object
}
}
$invtoryObjectsInHostnames
# Name Value
# ---- -----
# ip 10.0.0.1
# name server1
# ip 10.0.0.2
# name server2
另一种实现方式也涉及基于哈希查找,即使用一个哈希表,其每个键都是 name 在 $inventory 中的对象的 name,然后在 $hostnames 上完成过滤循环(这与你原本打算采用的方法类似,只是要高效得多):
$hostnames = @('server1', 'server2', 'server3', '...', 'server10000')
$inventory = @(
@{ name = 'server1'; ip = '10.0.0.1' }
@{ name = 'server2'; ip = '10.0.0.2' }
@{ name = 'notinhostnames'; ip = '10.0.0.2' })
$map = @{}
foreach ($object in $inventory) { $map[$object.name] = $object }
$invtoryObjectsInHostnames = foreach ($item in $hostnames) {
if ($map.ContainsKey($item)) {
$map[$item]
}
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。