我正在阅读一份教程,了解到PowerShell支持有序哈希表。我应该在什么情况下使用这个特性?
以下是我所说的示例代码:
$hash = [ordered]@{ ID = 1; Shape = "Square"; Color = "Blue"}
我正在阅读一份教程,了解到PowerShell支持有序哈希表。我应该在什么情况下使用这个特性?
以下是我所说的示例代码:
$hash = [ordered]@{ ID = 1; Shape = "Square"; Color = "Blue"}
让我从更广的角度来补充Maximilian Burszley的有用答案:
简而言之
大多数情况下,您需要使用[ordered] @{ ... }
([System.Collections.Specialized.OrderedDictionary]
)(PSv3+):
.Keys
和.Values
集合属性中)。[ordered] @{ ... }
与普通哈希表@{ ... }
、别名为[hashtable]
或[System.Collections.Hashtable]
互换使用,因为两种类型都实现了[IDictionary]
接口,这是接受哈希表参数的参数的通常类型。
使用[ordered]
所付出的性能惩罚是微不足道的。
一些背景:
由于技术原因,哈希表(散列表)的最有效实现方式是让条目的排序成为实现细节的结果,而不保证向调用者提供任何特定的顺序。
对于仅通过键执行隔离查找的用例,其中键(条目)之间的排序无关紧要,这很好。
然而,通常您确实关心条目的排序:
在最简单的情况下,用于显示目的; 混乱的定义顺序看起来令人不安; 例如:
@{ one = 1; two = 2; three = 3 }
Name Value
---- -----
one 1
three 3 # !!
two 2
更重要的是,条目的枚举可能需要可预测的进一步编程处理; 例如(注意:严格来说,JSON中属性顺序无关紧要,但对于人类观察者而言仍然很重要):
# 没有保证的属性顺序。
PS> @{ one = 1; two = 2; three = 3 } | ConvertTo-Json
{
"one": 1,
"three": 3, # !!
"two": 2
}
# 保证的属性顺序。
PS> [ordered] @{ one = 1; two = 2; three = 3 } | ConvertTo-Json
{
"one": 1,
"two": 2,
"three": 3
}
很遗憾的是,PowerShell的哈希表字面语法@{ ... }
不会默认为[ordered]
[1],但现在改变已经太晚了。
然而,在一个情境中,[ordered]
是被隐含的,那就是:如果你将一个哈希表字面量强制转换为[pscustomobject]
以创建一个自定义对象:
[pscustomobject] @{ ... }
是语法糖,等同于[pscustomobject] [ordered] @{ ... }
;也就是说,生成的自定义对象的属性是基于哈希表字面量的输入顺序排序的,例如:
PS> [pscustomobject] @{ one = 1; two = 2; three = 3 }
one two three # order preserved!
--- --- -----
1 2 3
PS> $ht = @{ one = 1; two = 2; three = 3 }; [pscustomobject] $ht
one three two # !! Order not preserved.
--- ----- ---
1 3 2
PS> [pscustomobject] (@{ one = 1; two = 2; three = 3 }) # Note the (...)
one three two # !! Order not preserved.
--- ----- ---
1 3 2
$oht = [ordered] @{} # Start with an empty *ordered* hashtable
# Add entries iteratively.
$i = 0
foreach ($name in 'one', 'two', 'three') {
$oht[$name] = ++$i
}
[pscustomobject] $oht # Convert the ordered hashtable to a custom object
[ordered]
只能应用于哈希表 字面量;您不能使用它将现有的常规哈希表转换为有序哈希表(这也没有任何意义,因为您一开始就没有定义顺序):PS> $ht = @{ one = 1; two = 2; three = 3 }; [ordered] $ht # !! Error
...
The ordered attribute can be specified only on a hash literal node.
...
顺便提一下:无论是有序哈希表还是普通哈希表,当它们通过管道发送时都不会枚举它们的条目;它们作为一个整体被发送。
要枚举这些条目,请使用.GetEnumerator()
方法,例如:
@{ one = 1; two = 2; three = 3 }.GetEnumerator() | ForEach-Object { $_.Value }
1
3 # !!
2
[ordered]
的性能影响:
如前所述,这是可以忽略不计的。以下是一些示例时间,平均分布在10,000次运行中,使用Time-Command
:Time-Command -Count 10000 { $ht=@{one=1;two=2;three=3;four=4;five=5;six=6;seven=7;eight=8;nine=9}; foreach($k in $ht.Keys){$ht.$k} },
{ $ht=[ordered] @{one=1;two=2;three=3;four=4;five=5;six=6;seven=7;eight=8;nine=9}; foreach($k in $ht.Keys){$ht.$k} }
示例时间(Windows PowerShell 5.1 在 Windows 10 上,单核 VM):
Command TimeSpan Factor
------- -------- ------
$ht=@{one=1;two=2;th... 00:00:00.0000501 1.00
$ht=[ordered] @{one=... 00:00:00.0000527 1.05
[ordered]
只导致了 5% 的减速。
[1] Maximilian Burszley 指出了一个特定于 [ordered]
哈希表的棘手问题:
对于数字键,区分键和索引可能会变得棘手;为了强制将数字解释为键,请将其转换为[object]
或使用点符号(.
,属性访问语法)而不是索引语法([...]
)::
# Ordered hashtable with numeric keys.
PS> $oht = [ordered] @{ 1 = 'one'; 2 = 'two' }
PS> $oht[1] # interpreted as *index* -> 2nd entry
two
PS> $oht[[object] 1] # interpreted as *key* -> 1st entry.
one
PS> $oht.1 # dot notation - interpreted as *key* -> 1st entry.
one
话虽如此,数字键并不常见,对我来说,默认使用可预测的枚举的好处胜过这个小问题。
[ordered]
下面的 .NET 类型 System.Collections.Specialized.OrderedDictionary
自 v1 以来就可用了,因此 PowerShell 可以选择它作为 @{ ... }
的默认实现,即使在 PowerShell v1 中也是如此。
考虑到 PowerShell 对向后兼容性的承诺,更改默认设置已不再是一个选项,因为这可能会破坏现有代码,即以下方式:
可能存在检查未经类型化的参数是否为哈希表的现有代码,使用 -is [hashtable]
,这将无法与有序哈希表一起使用(但使用 -is [System.Collections.IDictionary]
将可以)。
可能存在依赖具有数字键的哈希表的现有代码,在这种情况下,索引语法查找行为将发生变化(请参见上面的示例)。
PSCustomObject
并且您希望键按您输入的顺序排列,您可以使用ordered
。Export-Csv
时,标题是正确排序的。这只是我能想到的一个例子。按设计,hashtable
类型不关心您输入键/值的顺序,并且每次将其显示到成功流时都会有所不同。
ordered
字典的另一个用例:您可以将您的哈希表视为数组,并使用数字访问器查找项目,例如$myOrderedHash[-1]
将抓取添加到字典中的最后一个项。[pscustomobject]
时,您不需要使用[ordered]
。 - mklement0$hashtable[$dynamic] = $value
),并在最后进行强制转换 return [pscustomobject]$hashtable
。 - Maximilian Burszley[ordered]
。有太多不必要的 [pscustomobject] [ordered] @{ ... }
的例子存在。 - mklement0
[hashtable]
类型(我不确定您是否建议这样做,但调整反射行为的结果似乎是不明智的)。 - mklement0