当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 关于哈希表您想了解的一切

作者:精品下载站 日期:2024-12-14 03:04:27 浏览:12 分类:玩电脑

关于哈希表您想了解的一切


我想退一步谈谈哈希表。我现在一直在使用它们。昨晚我们的用户组会议结束后,我正在向某人传授它们,我意识到我对它们有和他一样的困惑。哈希表在 PowerShell 中非常重要,因此深入了解它们是有好处的。

笔记

本文的原始版本出现在@KevinMarquette 撰写的博客上。 PowerShell 团队感谢 Kevin 与我们分享这些内容。请查看他的博客:PowerShellExplained.com。

哈希表作为事物的集合

我希望您首先将哈希表视为哈希表传统定义中的集合。这个定义让您对它们稍后用于更高级的东西时如何工作有一个基本的了解。忽略这种理解常常会造成混乱。

什么是数组?

在深入了解哈希表是什么之前,我需要首先提到数组。出于本讨论的目的,数组是值或对象的列表或集合。

$array = @(1,2,3,5,7,11)

将项目放入数组后,您可以使用 foreach 迭代列表或使用索引访问数组中的各个元素。

foreach($item in $array)
{
    Write-Output $item
}

Write-Output $array[3]

您还可以以相同的方式使用索引更新值。

$array[2] = 13

我只是触及了数组的表面,但是当我转向哈希表时,这应该将它们放入正确的上下文中。

什么是哈希表?

我将从一般意义上的哈希表的基本技术描述开始,然后再转向 PowerShell 使用哈希表的其他方式。

哈希表是一种数据结构,很像数组,只不过您使用键存储每个值(对象)。这是一个基本的键/值存储。首先,我们创建一个空的哈希表。

$ageList = @{}

请注意,使用大括号而不是圆括号来定义哈希表。然后我们使用这样的键添加一个项目:

$key = 'Kevin'
$value = 36
$ageList.add( $key, $value )

$ageList.add( 'Alex', 9 )

这个人的名字是关键,他们的年龄是我要保存的值。

使用括号进行访问

将值添加到哈希表后,您可以使用相同的键将它们拉出来(而不是像数组那样使用数字索引)。

$ageList['Kevin']
$ageList['Alex']

当我想要凯文的年龄时,我使用他的名字来访问。我们也可以使用这种方法向哈希表添加或更新值。这就像使用上面的 add() 函数一样。

$ageList = @{}

$key = 'Kevin'
$value = 36
$ageList[$key] = $value

$ageList['Alex'] = 9

您可以使用另一种语法来访问和更新值,我将在后面的部分中介绍它。如果您从其他语言开始使用 PowerShell,这些示例应该符合您之前使用哈希表的方式。

使用值创建哈希表

到目前为止,我已经为这些示例创建了一个空的哈希表。您可以在创建键和值时预先填充它们。

$ageList = @{
    Kevin = 36
    Alex  = 9
}

作为查找表

这种类型的哈希表的真正价值在于您可以将它们用作查找表。这是一个简单的例子。

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

$server = $environments[$env]

在此示例中,您为 $env 变量指定环境,它将选择正确的服务器。您可以使用 switch($env){...} 进行这样的选择,但哈希表是一个不错的选择。

当您动态构建查找表以供以后使用时,这会变得更好。因此,当您需要交叉引用某些内容时,请考虑使用这种方法。我认为,如果 PowerShell 不太擅长使用 Where-Object 对管道进行过滤,我们会看到更多这种情况。如果您遇到性能很重要的情况,则需要考虑此方法。

我不会说它更快,但它确实符合“如果性能很重要,就测试它”的规则。

多重选择

通常,您将哈希表视为一对键/值对,您在其中提供一个键并获取一个值。 PowerShell 允许您提供键数组来获取多个值。

$environments[@('QA','DEV')]
$environments[('QA','DEV')]
$environments['QA','DEV']

在此示例中,我使用与上面相同的查找哈希表,并提供三种不同的数组样式来获取匹配项。这是 PowerShell 中隐藏的瑰宝,大多数人都不知道。

迭代哈希表

由于哈希表是键/值对的集合,因此对其进行迭代的方式与对数组或普通项目列表的迭代方式不同。

首先要注意的是,如果您对哈希表进行管道传输,管道会将其视为一个对象。

PS> $ageList | Measure-Object
count : 1

即使 .count 属性告诉您它包含多少个值。

PS> $ageList.count
2

如果您只需要值,则可以使用 .values 属性来解决此问题。

PS> $ageList.values | Measure-Object -Average
Count   : 2
Average : 22.5

枚举键并使用它们来访问值通常更有用。

PS> $ageList.keys | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_, $ageList[$_]
    Write-Output $message
}
Kevin is 36 years old
Alex is 9 years old

这是使用 foreach(){...} 循环的相同示例。

foreach($key in $ageList.keys)
{
    $message = '{0} is {1} years old' -f $key, $ageList[$key]
    Write-Output $message
}

我们遍历哈希表中的每个键,然后使用它来访问值。这是将哈希表作为集合使用时的常见模式。

GetEnumerator()

这让我们使用 GetEnumerator() 来迭代我们的哈希表。

$ageList.GetEnumerator() | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_.key, $_.value
    Write-Output $message
}

枚举器依次为您提供每个键/值对。它是专为该用例而设计的。感谢马克·克劳斯提醒我这一点。

BadEnumeration

一个重要的细节是,在枚举哈希表时您无法修改它。如果我们从基本的 $environments 示例开始:

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

尝试将每个键设置为相同的服务器值失败。

$environments.Keys | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

An error occurred while enumerating through a collection: Collection was modified;
enumeration operation may not execute.
+ CategoryInfo          : InvalidOperation: tableEnumerator:HashtableEnumerator) [],
 RuntimeException
+ FullyQualifiedErrorId : BadEnumeration

即使看起来应该没问题,这也会失败:

foreach($key in $environments.keys) {
    $environments[$key] = 'SrvDev03'
}

Collection was modified; enumeration operation may not execute.
    + CategoryInfo          : OperationStopped: (:) [], InvalidOperationException
    + FullyQualifiedErrorId : System.InvalidOperationException

解决这种情况的技巧是在进行枚举之前克隆键。

$environments.Keys.Clone() | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

哈希表作为属性的集合

到目前为止,我们放置在哈希表中的对象类型都是同一类型的对象。我在所有这些例子中都使用了年龄,关键是这个人的名字。当每个对象集合都有一个名称时,这是查看它的好方法。在 PowerShell 中使用哈希表的另一种常见方法是保存属性集合,其中键是属性名称。我将在下一个示例中探讨这个想法。

基于财产的访问

基于属性的访问的使用改变了哈希表的动态以及在 PowerShell 中使用它们的方式。这是上面我们将键视为属性的常见示例。

$ageList = @{}
$ageList.Kevin = 35
$ageList.Alex = 9

就像上面的示例一样,如果哈希表中尚不存在这些键,则此示例将添加这些键。根据您定义键的方式和值的不同,这要么有点奇怪,要么非常合适。到目前为止,年龄列表示例一直运行良好。我们需要一个新的例子来让这一切感觉正确。

$person = @{
    name = 'Kevin'
    age  = 36
}

我们可以像这样在 $person 上添加和访问属性。

$person.city = 'Austin'
$person.state = 'TX'

突然间,这个哈希表开始感觉和表现得像一个对象。它仍然是事物的集合,因此上面的所有示例仍然适用。我们只是从不同的角度来看待它。

检查键和值

在大多数情况下,您可以使用如下所示的方法来测试该值:

if( $person.age ){...}

它很简单,但对我来说是许多错误的根源,因为我忽略了逻辑中的一个重要细节。我开始用它来测试密钥是否存在。当值为 $false 或零时,该语句将意外返回 $false

if( $person.age -ne $null ){...}

这可以解决零值的问题,但不能解决 $null 与不存在键的问题。大多数时候,您不需要进行这种区分,但当您需要区分时,可以使用一些函数。

if( $person.ContainsKey('age') ){...}

我们还有一个 ContainsValue() 来应对您需要在不知道键或迭代整个集合的情况下测试值的情况。

移除和清除按键

您可以使用 .Remove() 函数删除键。

$person.remove('age')

为它们分配一个 $null 值只会让您得到一个具有 $null 值的键。

清除哈希表的常见方法是将其初始化为空哈希表。

$person = @{}

虽然这确实有效,但请尝试使用 clear() 函数。

$person.clear()

这是使用该函数创建自文档代码的实例之一,它使代码的意图非常清晰。

所有有趣的东西

有序哈希表

默认情况下,哈希表没有排序(或排序)。在传统环境中,当您始终使用键来访问值时,顺序并不重要。您可能会发现您希望属性保持您定义的顺序。值得庆幸的是,有一种方法可以使用 ordered 关键字来做到这一点。

$person = [ordered]@{
    name = 'Kevin'
    age  = 36
}

现在,当您枚举键和值时,它们将保持该顺序。

内联哈希表

当您在一行上定义哈希表时,可以用分号分隔键/值对。

$person = @{ name = 'kevin'; age = 36; }

如果您在管道上创建它们,这会派上用场。

常见管道命令中的自定义表达式

有一些 cmdlet 支持使用哈希表来创建自定义或计算属性。您通常会在 Select-ObjectFormat-Table 中看到这种情况。哈希表有一个特殊的语法,完全展开时看起来像这样。

$property = @{
    name = 'totalSpaceGB'
    expression = { ($_.used + $_.free) / 1GB }
}

name 是 cmdlet 为该列添加的标签。 表达式 是一个执行的脚本块,其中$_ 是管道上对象的值。这是正在运行的脚本:

$drives = Get-PSDrive | Where Used
$drives | Select-Object -Property name, $property

Name     totalSpaceGB
----     ------------
C    238.472652435303

我将其放置在一个变量中,但它可以轻松地内联定义,并且您可以将 name 缩短为 n 并将 expression 缩短为 e 当你在做的时候。

$drives | Select-Object -property name, @{n='totalSpaceGB';e={($_.used + $_.free) / 1GB}}

我个人不喜欢发出命令的时间太长,而且它经常会引发一些我不会参与的不良行为。我更有可能使用我想要的所有字段和属性创建一个新的哈希表或 pscustomobject,而不是在脚本中使用这种方法。但是有很多代码可以做到这一点,所以我希望您能够意识到这一点。稍后我会讨论创建一个pscustomobject

自定义排序表达式

如果对象具有您想要排序的数据,那么对集合进行排序就很容易。您可以在对对象进行排序之前将数据添加到对象中,也可以为 Sort-Object 创建自定义表达式。

Get-ADUser | Sort-Object -Property @{ e={ Get-TotalSales $_.Name } }

在此示例中,我将获取用户列表并使用一些自定义 cmdlet 来获取用于排序的附加信息。

对哈希表列表进行排序

如果您有一个要排序的哈希表列表,您会发现 Sort-Object 不会将您的键视为属性。我们可以通过使用自定义排序表达式来完成这一轮。

$data = @(
    @{name='a'}
    @{name='c'}
    @{name='e'}
    @{name='f'}
    @{name='d'}
    @{name='b'}
)

$data | Sort-Object -Property @{e={$_.name}}

在 cmdlet 上生成哈希表

这是我最喜欢的关于哈希表的事情之一,但很多人早期都没有发现。这个想法是,您可以首先将它们打包到哈希表中,而不是在一行中向 cmdlet 提供所有属性。然后你可以以特殊的方式将哈希表赋予该函数。以下是按正常方式创建 DHCP 作用域的示例。

Add-DhcpServerV4Scope -Name 'TestNetwork' -StartRange '10.0.0.2' -EndRange '10.0.0.254' -SubnetMask '255.255.255.0' -Description 'Network for testlab A' -LeaseDuration (New-TimeSpan -Days 8) -Type "Both"

如果不使用展开,所有这些事情都需要在一行上定义。它要么滚动离开屏幕,要么会在任何需要的地方换行。现在将其与使用展开的命令进行比较。

$DHCPScope = @{
    Name          = 'TestNetwork'
    StartRange    = '10.0.0.2'
    EndRange      = '10.0.0.254'
    SubnetMask    = '255.255.255.0'
    Description   = 'Network for testlab A'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type          = "Both"
}
Add-DhcpServerV4Scope @DHCPScope

使用 @ 符号而不是 $ 来调用 splat 操作。

请花点时间体会一下这个示例是多么容易阅读。它们是完全相同的命令,具有相同的值。第二个更容易理解和维护。

每当命令变得太长时我都会使用泼溅。我将太长定义为导致窗口向右滚动。如果我找到一个函数的三个属性,我很可能会使用散列表重写它。

可选参数的泼溅

我使用展开的最常见方法之一是处理来自脚本中其他位置的可选参数。假设我有一个封装 Get-CIMInstance 调用的函数,该调用具有可选的 $Credential 参数。

$CIMParams = @{
    ClassName = 'Win32_Bios'
    ComputerName = $ComputerName
}

if($Credential)
{
    $CIMParams.Credential = $Credential
}

Get-CIMInstance @CIMParams

我首先使用通用参数创建哈希表。然后我添加 $Credential(如果存在)。因为我在这里使用了 splatting,所以只需在代码中调用一次 Get-CIMInstance 即可。这种设计模式非常干净,可以轻松处理大量可选参数。

公平地说,您可以编写命令以允许参数使用 $null 值。您只是并不总是能够控制您正在调用的其他命令。

多个碎片

您可以将多个哈希表分配到同一个 cmdlet。如果我们重新审视最初的泼溅示例:

$Common = @{
    SubnetMask  = '255.255.255.0'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type = "Both"
}

$DHCPScope = @{
    Name        = 'TestNetwork'
    StartRange  = '10.0.0.2'
    EndRange    = '10.0.0.254'
    Description = 'Network for testlab A'
}

Add-DhcpServerv4Scope @DHCPScope @Common

当我有一组要传递给许多命令的通用参数时,我将使用此方法。

干净代码的泼溅

如果使您的代码更简洁,那么分散单个参数并没有什么问题。

$log = @{Path = '.\logfile.log'}
Add-Content "logging this command" @log

喷射可执行文件

Splatting 也适用于某些使用 /param:value 语法的可执行文件。例如,Robocopy.exe 就有一些这样的参数。

$robo = @{R=1;W=1;MT=8}
robocopy source destination @robo

我不知道这有多大用处,但我发现它很有趣。

添加哈希表

哈希表支持加法运算符来组合两个哈希表。

$person += @{Zip = '78701'}

仅当两个哈希表不共享密钥时,这才有效。

嵌套哈希表

我们可以使用哈希表作为哈希表内的值。

$person = @{
    name = 'Kevin'
    age  = 36
}
$person.location = @{}
$person.location.city = 'Austin'
$person.location.state = 'TX'

我从包含两个键的基本哈希表开始。我添加了一个名为 location 的键,其中包含一个空哈希表。然后我将最后两项添加到该 location 哈希表中。我们也可以内联完成这一切。

$person = @{
    name = 'Kevin'
    age  = 36
    location = @{
        city  = 'Austin'
        state = 'TX'
    }
}

这将创建与我们上面看到的相同的哈希表,并且可以以相同的方式访问属性。

$person.location.city
Austin

有很多方法可以处理对象的结构。这是查看嵌套哈希表的第二种方法。

$people = @{
    Kevin = @{
        age  = 36
        city = 'Austin'
    }
    Alex = @{
        age  = 9
        city = 'Austin'
    }
}

这混合了使用哈希表作为对象集合和属性集合的概念。即使使用您喜欢的任何方法嵌套这些值,仍然可以轻松访问它们。

PS> $people.kevin.age
36
PS> $people.kevin['city']
Austin
PS> $people['Alex'].age
9
PS> $people['Alex']['City']
Austin

当我将其视为属性时,我倾向于使用点属性。这些通常是我在代码中静态定义的东西,我立刻就知道它们。如果我需要遍历列表或以编程方式访问键,我会使用括号来提供键名称。

foreach($name in $people.keys)
{
    $person = $people[$name]
    '{0}, age {1}, is in {2}' -f $name, $person.age, $person.city
}

嵌套哈希表的能力为您提供了很多灵活性和选择。

查看嵌套哈希表

一旦开始嵌套哈希表,您将需要一种简单的方法来从控制台查看它们。如果我采用最后一个哈希表,我会得到如下所示的输出,而且它的深度只有这么深:

PS> $people
Name                           Value
----                           -----
Kevin                          {age, city}
Alex                           {age, city}

我用于查看这些内容的命令是 ConvertTo-JSON 因为它非常干净,而且我经常在其他内容上使用 JSON。

PS> $people | ConvertTo-Json
{
    "Kevin":  {
                "age":  36,
                "city":  "Austin"
            },
    "Alex":  {
                "age":  9,
                "city":  "Austin"
            }
}

即使您不了解 JSON,您也应该能够看到您要查找的内容。对于这样的结构化数据有一个 Format-Custom 命令,但我仍然更喜欢 JSON 视图。

创建对象

有时您只需要一个对象,而使用哈希表来保存属性并不能完成工作。最常见的是,您希望将键视为列名称。 pscustomobject 让这一切变得简单。

$person = [pscustomobject]@{
    name = 'Kevin'
    age  = 36
}

$person

name  age
----  ---
Kevin  36

即使您最初没有将其创建为 pscustomobject,您也可以在以后需要时对其进行强制转换。

$person = @{
    name = 'Kevin'
    age  = 36
}

[pscustomobject]$person

name  age
----  ---
Kevin  36

我已经写了关于 pscustomobject 的详细文章,您应该在这篇文章之后阅读。它建立在在这里学到的很多东西的基础上。

读取哈希表并将其写入文件

保存为 CSV

努力将哈希表保存到 CSV 是我上面提到的困难之一。将哈希表转换为 pscustomobject,它将正确保存为 CSV。如果您从 pscustomobject 开始,这样会保留列顺序,这会有所帮助。但如果需要,您可以将其转换为内联 pscustomobject

$person | ForEach-Object{ [pscustomobject]$_ } | Export-CSV -Path $path

再次查看我关于使用 pscustomobject 的文章。

将嵌套哈希表保存到文件

如果我需要将嵌套哈希表保存到文件中,然后再次读回它,我会使用 JSON cmdlet 来完成此操作。

$people | ConvertTo-JSON | Set-Content -Path $path
$people = Get-Content -Path $path -Raw | ConvertFrom-JSON

这个方法有两点很重要。首先,JSON 是多行写出的,因此我需要使用 -Raw 选项将其读回单个字符串。第二个是导入的对象不再是[hashtable]。它现在是一个 [pscustomobject],如果您不希望它出现,可能会导致问题。

留意深度嵌套的哈希表。当您将其转换为 JSON 时,您可能无法获得预期的结果。

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json

{
  "a": {
    "b": {
      "c": "System.Collections.Hashtable"
    }
  }
}

使用深度参数确保您已展开所有嵌套哈希表。

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json -Depth 3

{
  "a": {
    "b": {
      "c": {
        "d": "e"
      }
    }
  }
}

如果您需要在导入时将其作为[hashtable],则需要使用Export-CliXmlImport-CliXml 命令。

将 JSON 转换为哈希表

如果您需要将 JSON 转换为 [hashtable],我知道有一种方法可以使用 .NET 中的 JavaScriptSerializer 来完成此操作。

[Reflection.Assembly]::LoadWithPartialName("System.Web.Script.Serialization")
$JSSerializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new()
$JSSerializer.Deserialize($json,'Hashtable')

从 PowerShell v6 开始,JSON 支持使用 NewtonSoft JSON.NET 并添加了哈希表支持。

'{ "a": "b" }' | ConvertFrom-Json -AsHashtable

Name      Value
----      -----
a         b

PowerShell 6.2 在 ConvertFrom-Json 中添加了 Depth 参数。默认深度为 1024。

直接从文件读取

如果您有一个包含使用 PowerShell 语法的哈希表的文件,则有一种方法可以直接导入它。

$content = Get-Content -Path $Path -Raw -ErrorAction Stop
$scriptBlock = [scriptblock]::Create( $content )
$scriptBlock.CheckRestrictedLanguage( $allowedCommands, $allowedVariables, $true )
$hashtable = ( & $scriptBlock )

它将文件的内容导入到 scriptblock 中,然后在执行之前进行检查以确保其中没有任何其他 PowerShell 命令。

关于这一点,您是否知道模块清单(psd1 文件)只是一个哈希表?

键可以是任何对象

大多数时候,键只是字符串。所以我们可以在任何东西上加上引号并使其成为关键。

$person = @{
    'full name' = 'Kevin Marquette'
    '#' = 3978
}
$person['full name']

你可以做一些你可能没有意识到自己可以做的奇怪的事情。

$person.'full name'

$key = 'full name'
$person.$key

仅仅因为您可以做某事,并不意味着您应该做某事。最后一个看起来像是一个等待发生的错误,并且很容易被阅读您代码的任何人误解。

从技术上讲,您的密钥不必是字符串,但如果您只使用字符串,则更容易考虑它们。但是,索引不适用于复杂的键。

$ht = @{ @(1,2,3) = "a" }
$ht

Name                           Value
----                           -----
{1, 2, 3}                      a

通过键访问哈希表中的值并不总是有效。例如:

$key = $ht.keys[0]
$ht.$($key)
a
$ht[$key]
a

当键是数组时,必须将 $key 变量包装在子表达式中,以便它可以与成员访问 (.) 表示法一起使用。或者,您可以使用数组索引 ([]) 表示法。

在自动变量中使用

$PSBoundParameters

$PSBoundParameters 是一个仅存在于函数上下文中的自动变量。它包含调用该函数所使用的所有参数。这并不完全是一个哈希表,但足够接近,您可以将其视为哈希表。

这包括删除按键并将其分配给其他功能。如果您发现自己正在编写代理函数,请仔细看看这个。

有关更多详细信息,请参阅 about_Automatic_Variables。

PSBoundParameters 陷阱

要记住的一件重要事情是,这仅包括作为参数传入的值。如果您还有带有默认值的参数,但调用者未传入这些参数,则 $PSBoundParameters 不包含这些值。这一点常常被忽视。

$PSDefaultParameterValues

此自动变量允许您为任何 cmdlet 分配默认值,而无需更改 cmdlet。看一下这个例子。

$PSDefaultParameterValues["Out-File:Encoding"] = "UTF8"

这会向 $PSDefaultParameterValues 哈希表添加一个条目,将 UTF8 设置为 Out-File -Encoding 参数的默认值。这是特定于会话的,因此您应该将其放置在您的 $profile 中。

我经常使用它来预先分配我经常键入的值。

$PSDefaultParameterValues[ "Connect-VIServer:Server" ] = 'VCENTER01.contoso.local'

这也接受通配符,因此您可以批量设置值。您可以使用以下一些方法:

$PSDefaultParameterValues[ "Get-*:Verbose" ] = $true
$PSDefaultParameterValues[ "*:Credential" ] = Get-Credential

有关更深入的详细信息,请参阅 Michael Sorens 撰写的关于自动默认值的精彩文章。

正则表达式$匹配

当您使用 -match 运算符时,会使用匹配结果创建一个名为 $matches 的自动变量。如果您的正则表达式中有任何子表达式,这些子匹配也会列出。

$message = 'My SSN is 123-45-6789.'

$message -match 'My SSN is (.+)\.'
$Matches[0]
$Matches[1]

命名比赛

这是我最喜欢的功能之一,但大多数人不知道。如果您使用命名正则表达式匹配,则可以通过匹配的名称访问该匹配。

$message = 'My Name is Kevin and my SSN is 123-45-6789.'

if($message -match 'My Name is (?<Name>.+) and my SSN is (?<SSN>.+)\.')
{
    $Matches.Name
    $Matches.SSN
}

在上面的示例中,(?.*) 是一个命名子表达式。然后将该值放置在 $Matches.Name 属性中。

组对象-AsHashtable

Group-Object 的一个鲜为人知的功能是它可以将一些数据集转换为哈希表。

Import-CSV $Path | Group-Object -AsHashtable -Property email

这会将每一行添加到哈希表中,并使用指定的属性作为访问它的键。

复制哈希表

要知道的一件重要的事情是哈希表是对象。每个变量只是一个对象的引用。这意味着需要更多的工作来制作哈希表的有效副本。

分配参考类型

当您有一个哈希表并将其分配给第二个变量时,这两个变量都指向同一个哈希表。

PS> $orig = @{name='orig'}
PS> $copy = $orig
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [copy]

这强调了它们是相同的,因为改变一个中的值也会改变另一个中的值。当将哈希表传递给其他函数时,这也适用。如果这些函数对该哈希表进行了更改,那么您的原始哈希表也会被更改。

浅拷贝,单层

如果我们有一个像上面的示例一样的简单哈希表,我们可以使用 .Clone() 来制作浅表副本。

PS> $orig = @{name='orig'}
PS> $copy = $orig.Clone()
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [orig]

这将使我们能够对其中一个进行一些基本的更改,而不会影响另一个。

浅拷贝、嵌套

之所以称为浅复制,是因为它只复制基本级别的属性。如果这些属性之一是引用类型(如另一个哈希表),那么这些嵌套对象仍将指向彼此。

PS> $orig = @{
        person=@{
            name='orig'
        }
    }
PS> $copy = $orig.Clone()
PS> $copy.person.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.person.name
PS> 'Orig: [{0}]' -f $orig.person.name

Copy: [copy]
Orig: [copy]

所以你可以看到,即使我克隆了哈希表,对 person 的引用也没有被克隆。我们需要进行深度复制才能真正拥有不链接到第一个哈希表的第二个哈希表。

深拷贝

有几种方法可以制作哈希表的深层副本(并将其保留为哈希表)。下面是一个使用 PowerShell 递归创建深层副本的函数:

function Get-DeepClone
{
    [CmdletBinding()]
    param(
        $InputObject
    )
    process
    {
        if($InputObject -is [hashtable]) {
            $clone = @{}
            foreach($key in $InputObject.keys)
            {
                $clone[$key] = Get-DeepClone $InputObject[$key]
            }
            return $clone
        } else {
            return $InputObject
        }
    }
}

它不处理任何其他引用类型或数组,但它是一个很好的起点。

另一种方法是使用 .Net 使用 CliXml 对其进行反序列化,如下函数所示:

function Get-DeepClone
{
    param(
        $InputObject
    )
    $TempCliXmlString = [System.Management.Automation.PSSerializer]::Serialize($obj, [int32]::MaxValue)
    return [System.Management.Automation.PSSerializer]::Deserialize($TempCliXmlString)
}

对于非常大的哈希表,反序列化函数随着横向扩展而变得更快。但是,使用此方法时需要考虑一些事项。由于它使用 CliXml,因此它会占用大量内存,如果您要克隆巨大的哈希表,这可能会成为问题。 CliXml 的另一个限制是深度限制为 48。这意味着,如果您的哈希表有 48 层嵌套哈希表,则克隆将失败并且根本不会输出哈希表。

还要别的吗?

我很快就了解了很多内容。我希望您每次阅读本文时都能学到新的东西或更好地理解它。因为我涵盖了此功能的全部范围,所以有些方面现在可能不适用于您。这是完全可以的,并且是预期的,具体取决于您使用 PowerShell 的程度。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯