为什么要在PowerShell中使用有序散列?

6l7fqoea  于 2023-04-12  发布在  Shell
关注(0)|答案(2)|浏览(131)

我正在阅读教程,了解到PowerShell支持有序哈希。我什么时候会使用该功能?
我所谈论的示例代码:

$hash = [ordered]@{ ID = 1; Shape = "Square"; Color = "Blue"}
8oomwypt

8oomwypt1#

让我用更广阔的视角补充Maximilian Burszley's helpful answer

tl;dr
****大多数情况下 * 您需要[ordered] @{ ... }**([System.Collections.Specialized.OrderedDictionary])(PSv 3+):

  • 它提供了条目的枚举 * 按照它们被定义的顺序 *(也反映在.Keys.Values集合属性中)。
  • 它还允许通过 index 访问条目,如数组。
  • 通常,您可以将[ordered] @{ ... }与常规哈希表@{ ... }[hashtable],也称为[System.Collections.Hashtable])互换使用,因为这两种类型都实现了[IDictionary] interface,这是接受哈希表的参数的典型类型。

使用[ordered]所带来的性能损失可以忽略不计。
一些背景:

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

这对于您所做的只是按键执行隔离查找的用例是很好的,其中键(条目)之间的顺序是无关紧要的。
但是,您通常会关心条目的顺序:

  • 在最简单的情况下,用于 * 显示 * 目的;看到定义顺序混乱,会让人感到不安;例如:
@{ one = 1; two = 2; three = 3 } 

  Name                           Value
  ----                           -----
  one                            1
  three                          3   # !! 
  two                            2
  • 更重要的是,条目的枚举可能需要是可预测的,以用于进一步的编程处理;例如(注:严格地说,属性顺序在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
  }

**不幸的是,PowerShell的hashtable-literal语法@{ ... }并不 * 默认为 * [ordered] [1],但现在改变已经太晚了。
有一个上下文中[ordered] * 是 * 隐含的,然而:如果您将hashtable literal 转换为[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

因此,如果你先 * 迭代地 * 构造一个哈希表,然后 * 将其转换为[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

最后,注意[ordered]只能应用于hashtable literal;你不能用它来将一个已经存在的常规哈希表转换成有序哈希表(这没有任何意义,因为你没有定义开始的顺序):

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]的性能影响
如前所述,它是可忽略的;以下是使用Time-Command计算的10,000次运行的平均采样时间:

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 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

也就是说,[ordered]的增长速度仅下降了5%。
[1]马克西米利安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

也就是说,数字键并不常见,对我来说,默认为可预测枚举的好处超过了这个小问题。
.NET类型的基础[ordered]System.Collections.Specialized.OrderedDictionary,从v1开始就可用,所以PowerShell可以从一开始就选择它作为@{ ... }的默认实现,即使在PowerShell v1中也是如此。
考虑到PowerShell对向后兼容性的承诺,更改默认值不再是一种选择,因为这可能会破坏现有代码,即以以下方式:

  • 可能有现有的代码检查无类型参数是否是带有-is [hashtable]的哈希表,这将不再适用于有序哈希表(但是,使用-is [System.Collections.IDictionary] * 检查将 * 工作)。
  • 可能存在依赖于具有数字键的哈希表的现有代码,在这种情况下,索引语法查找行为将改变(参见上面的示例)。
amrnrhlw

amrnrhlw2#

使用ordered字典的原因是为了显示/类型转换的目的。例如,如果你想将hashtable转换为PSCustomObject,并且你想让你的键按照你输入它们的顺序,你可以使用ordered
这里的用例是,当你使用Export-Csv时,头的顺序是正确的。这只是我能想到的一个例子。通过设计,hashtable类型不关心你输入键/值的顺序,每次你将它显示到成功流时都会不同。
ordered字典的另一个用例:你可以把你的哈希表当作一个数组,并使用数字访问器来查找项目,比如$myOrderedHash[-1]将抓取添加到字典中的最后一个项目。

相关问题