为什么我会在PowerShell中使用有序哈希?

Dum*_*bie 9 powershell

我正在阅读教程并了解到PowerShell支持有序哈希.我什么时候会使用该功能?

我正在谈论的样本:

$hash = [ordered]@{ ID = 1; Shape = "Square"; Color = "Blue"}
Run Code Online (Sandbox Code Playgroud)

The*_*le1 9

ordered字典的原因是用于显示/类型转换目的.例如,如果您想要将您的密钥转换hashtable为a,PSCustomObject并且您希望密钥符合您输入的顺序,则可以使用ordered.

这里的用例是当你使用时Export-Csv,标题的顺序是正确的.这只是我能想到的一个例子.按照设计,hashtable类型不关心输入键/值的顺序,每次将其显示到成功流时都会有所不同.

ordered字典的另一个用例:您可以将哈希表视为数组并使用数字访问器来查找项目,例如$myOrderedHash[-1]将获取添加到字典中的最后一项.

  • @mklement0 有时我使用哈希表在整个函数中构建一个对象(`$hashtable[$dynamic] = $value`)并在最后将它转换为 `return [pscustomobject]$hashtable` (2认同)

mkl*_*nt0 7

让我以更广阔的视角补充TheIncorrigible1的有用答案:

TL;博士

您支付使用的性能损失可以[ordered]忽略不计.


一些背景:

由于技术原因,哈希表(哈希表)的最有效实现让条目的排序成为实现细节的结果,而不保证对调用者的任何特定顺序.

这适用于所有操作都是按键执行隔离查找的用例,其中键(条目)之间的顺序无关紧要.

但是,通常您会关注条目的排序:

  • 在最简单的情况下,用于显示目的; 看到定义顺序混乱,有些令人不安; 例如:

    @{ one = 1; two = 2; three = 3 } 
    
    Name                           Value
    ----                           -----
    one                            1
    three                          3   # !! 
    two                            2
    
    Run Code Online (Sandbox Code Playgroud)
  • 更重要的是,可能需要对条目的列举进行进一步的程序化处理; 例如(注意:严格来说,属性顺序在JSON中无关紧要,但对人类观察者来说也很重要):

    # No guaranteed property order.
    PS> @{ one = 1; two = 2; three = 3 } | ConvertTo-Json
    {
     "one": 1,
     "three": 3, # !!
     "two": 2
    }
    
    # Guaranteed property order.
    PS> [ordered] @{ one = 1; two = 2; three = 3 } | ConvertTo-Json
    {
      "one": 1,
      "two": 2,
      "three": 3
    }
    
    Run Code Online (Sandbox Code Playgroud)

不幸的是,PowerShell的hashtable-literal语法@{ ... }并没有默认为 [ordered][1],但改变它已经太晚了.

有一个上下文中[ordered] 暗示,但是:如果你投一个哈希表字面[pscustomobject],以创建自定义对象:

[pscustomobject] @{ ... }语法糖[pscustomobject] [ordered] @{ ... } ; 也就是说,生成的自定义对象的属性是根据哈希表文本中的条目顺序排序的; 例如:

PS> [pscustomobject] @{ one = 1; two = 2; three = 3 }

one two three  # order preserved!
--- --- -----
  1   2     3
Run Code Online (Sandbox Code Playgroud)

但请注意,这仅与上面所示完全相同:如果转换直接应用于散列表文字 ; 如果您首先使用变量来存储哈希表,或者如果您只是将(...)序列中的文字括起来,则会丢失:

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
Run Code Online (Sandbox Code Playgroud)

因此,如果首先迭代地构造哈希表然后将其转换为哈希表,[pscustomobject]必须从[ordered]哈希表开始以获得可预测的属性排序; 这种技术很有用,因为创建哈希表条目比向自定义对象添加属性更容易; 例如:

$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
Run Code Online (Sandbox Code Playgroud)

最后,请注意,[ordered]只能应用于哈希表文字 ; 您不能使用它将预先存在的常规哈希表转换为有序哈希表(无论如何都没有任何意义,因为您没有定义的顺序开始):

PS> $ht = @{ one = 1; two = 2; three = 3 }; [ordered] $ht # !! Error
...
The ordered attribute can be specified only on a hash literal node.
...
Run Code Online (Sandbox Code Playgroud)

旁注:当通过管道发送时,有序或常规哈希表都不会枚举它们的条目 ; 它们是作为一个整体发送的.
要枚举条目,请使用该.GetEnumerator()方法 ; 例如:

@{ one = 1; two = 2; three = 3 }.GetEnumerator() | ForEach-Object { $_.Value }
1
3  # !!
2
Run Code Online (Sandbox Code Playgroud)

至于使用的性能影响[ordered]:

如上所述,它可以忽略不计 ; 以下是一些样本计时,平均10,000次运行,使用Time-Command:

Time-Command -Count 10,000 { $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} }
Run Code Online (Sandbox Code Playgroud)

示例计时(Windows 10上的Windows PowerShell 5.1,单核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  
Run Code Online (Sandbox Code Playgroud)

也就是说,[ordered]仅减速仅为5%.


[1] TheIncorrigible1指出了一个特定于[ordered]哈希表的棘手方面:

使用数字,区分索引可能会变得棘手; 强制将数字解释为,将其强制转换为[object]:

# Ordered hashtable with numeric keys.
PS> $oht = [ordered] @{ 1 = 'one'; 2 = 'two' }

PS> $oht[1]  # same as $oht.1 - interpreted as *index* -> 2nd entry
two

PS> $oht[[object] 1] # interpreted as *key* -> 1st entry.
one
Run Code Online (Sandbox Code Playgroud)

也就是说,数字键并不常见,对我来说,默认可预测枚举的好处超过了这个小问题.

.NET框架类型底层[ordered], System.Collections.Specialized.OrderedDictionary自v1以来就已经可用,因此@{ ... }即使在PowerShell v1中,PowerShell也可以从一开始就选择它作为默认实现.
鉴于PowerShell对向后兼容性的承诺,更改默认值不再是一种选择,因为这可能会破坏现有代码.

  • @hyde:虽然_一些_事情已经被破坏,特别是在转向PowerShell_Core_之后,但不幸的是,目前的总体承诺仍然是“永远向后兼容”。什么可以打破?使用反射来查看传递的类型的代码;使用带有数字键的哈希表的代码会突然执行索引访问,仅举两个例子。 (2认同)