+=
) 经常用于 StackOverflow 站点的 PowerShell
问题和答案中来构造集合对象,例如:
$Collection = @()
1..$Size | ForEach-Object {
$Collection += [PSCustomObject]@{Index = $_; Name = "Name$_"}
}
然而,这似乎是一种非常低效的操作。
一般来说,在 PowerShell 中构建对象集合时应避免使用增加赋值运算符 (
+=
),这样可以吗?
是的,构建对象集合时应避免使用增加赋值运算符 (
+=
),另请参阅:PowerShell 脚本性能注意事项。+=
运算符通常需要更多语句(因为数组初始化 = @()
)并且它鼓励将整个集合存储在内存中而不是中间将其推入管道这一事实之外,它效率低下 .
它效率低下的原因是因为每次使用
+=
运算符时,它只会执行:
$Collection = $Collection + $NewObject
因为数组在元素数量方面是不可变的,所以每次迭代都会重新创建整个集合。
正确的 PowerShell 语法是:
$Collection = 1..$Size | ForEach-Object {
[PSCustomObject]@{Index = $_; Name = "Name$_"}
}
注意: 与其他 cmdlet 一样;如果只有一项(迭代),输出将是一个 scalar 而不是数组,要将其强制为数组,您可以使用
[Array]
类型:[Array]$Collection = 1..$Size | ForEach-Object { ... }
或使用 Array 子表达式运算符@( )
:$Collection = @(1..$Size | ForEach-Object { ... })
建议不甚至将结果存储在变量中(
$a = ...
),而是立即将其传递到管道中以节省内存,例如:
1..$Size | ForEach-Object {
[PSCustomObject]@{Index = $_; Name = "Name$_"}
} | ConvertTo-Csv .\Outfile.csv
System.Collections.ArrayList
类,这通常几乎与PowerShell管道一样快,但缺点是它比(正确)使用PowerShell管道消耗更多内存.
另请参阅:从数组的属性获取唯一索引项的最快方法和导致“system.outofmemoryexception”的数组
为了显示集合大小和性能下降之间的关系,您可以检查以下测试结果:
1..20 | ForEach-Object {
$size = 1000 * $_
$Performance = @{Size = $Size}
$Performance.Pipeline = (Measure-Command {
$Collection = 1..$Size | ForEach-Object {
[PSCustomObject]@{Index = $_; Name = "Name$_"}
}
}).Ticks
$Performance.Increase = (Measure-Command {
$Collection = @()
1..$Size | ForEach-Object {
$Collection += [PSCustomObject]@{Index = $_; Name = "Name$_"}
}
}).Ticks
[pscustomobject]$Performance
} | Format-Table *,@{n='Factor'; e={$_.Increase / $_.Pipeline}; f='0.00'} -AutoSize
Size Increase Pipeline Factor
---- -------- -------- ------
1000 1554066 780590 1.99
2000 4673757 1084784 4.31
3000 10419550 1381980 7.54
4000 14475594 1904888 7.60
5000 23334748 2752994 8.48
6000 39117141 4202091 9.31
7000 52893014 3683966 14.36
8000 64109493 6253385 10.25
9000 88694413 4604167 19.26
10000 104747469 5158362 20.31
11000 126997771 6232390 20.38
12000 148529243 6317454 23.51
13000 190501251 6929375 27.49
14000 209396947 9121921 22.96
15000 244751222 8598125 28.47
16000 286846454 8936873 32.10
17000 323833173 9278078 34.90
18000 376521440 12602889 29.88
19000 422228695 16610650 25.42
20000 475496288 11516165 41.29
这意味着,对于集合大小为
20,000
的对象,使用 +=
运算符比使用 PowerShell 管道慢约 40x
。
显然有些人很难纠正已经使用增加赋值运算符的脚本(
+=
)。因此,我创建了一个小指令来执行此操作:
<variable> +=
分配,仅保留对象项。通过not分配对象,对象将被简单地放入管道中。
ForEach ( ... ) {
$Array += $Object1
$Array += $Object2
ForEach ( ... ) {
$Array += $Object3
$Array += Get-Object
}
}
本质上与:
相同ForEach ( ... ) {
$Object1
$Object2
ForEach ( ... ) {
$Object3
Get-Object
}
}
注意:如果没有迭代,可能没有理由更改您的脚本,因为可能只涉及一些添加内容
$Array = @()
)。例如:
$Array = ForEach ( ... ) { ...
注 1: 同样,如果您希望单个对象充当数组,您可能需要使用 Array 子表达式运算符
@( )
但您也可以在使用时考虑这样做数组,例如:@($Array).Count
或 ForEach ($Item in @($Array))
... | ForEach-Object {...} | Export-Csv .\File.csv
。
<Variable> = @()
有关完整示例,请参阅:在 Powershell 中比较数组
请注意,这同样适用于使用
+=
构建 strings (
请参阅:PowerShell 中是否有字符串连接快捷方式?)并构建 HashTables,例如:
$HashTable += @{ $NewName = $Value }