【问题标题】:PowerShell array initializationPowerShell 数组初始化
【发布时间】:2010-09-18 14:27:27
【问题描述】:

在 PowerShell 中初始化数组的最佳方法是什么?

例如代码

$array = @()
for($i=0; $i -lt 5;$i++)
{
    $array[$i] = $FALSE
}

产生错误

Array assignment failed because index '0' was out of range.
At H:\Software\PowerShell\TestArray.ps1:4 char:10
+         $array[$ <<<< i] = $FALSE

【问题讨论】:

  • 告诉我们您要完成的工作,也许我们可以为您提供更好的“惯用 PowerShell”答案。我从来不需要在 PowerShell 中新建一个数组。
  • 我没有看到有人特别提到数组是不可变的。一旦创建,就无法修改。

标签: arrays powershell


【解决方案1】:

这里还有另外两种方法,都很简洁。

$arr1 = @(0) * 20
$arr2 = ,0 * 20

【讨论】:

  • 非常好,我今天早上试图解决这个问题,我认为您提供了最简洁的初始化数组的方法。
  • 我一定很厚。有人可以解释这是在做什么以及 * 20 是做什么用的吗? 20 不会出现在其他人的答案或问题中。
  • 看起来像 powershell 数组采用乘法运算符,它只是多次复制自身。很酷。
  • @halr9000 您的链接已损坏。这就是为什么我们发现将相关信息放入答案本身很有用。 :)
  • 20 表示要初始化的数组元素个数。因此,$arr1[21] = ... 会产生错误。
【解决方案2】:

如果您希望创建类型化数组,也可以依赖构造函数的默认值

> $a = new-object bool[] 5
> $a
False
False
False
False
False

bool 的默认值显然是 false 所以这适用于您的情况。同样,如果您创建一个类型化的 int[] 数组,您将获得默认值 0。

我用来初始化数组的另一种很酷的方法是使用以下速记:

> $a = ($false, $false, $false, $false, $false)
> $a
False
False
False
False
False

或者,如果您想初始化一个范围,我有时会发现这很有用:

> $a = (1..5) > $一个 1 2 3 4 5

希望这有点帮助!

【讨论】:

  • 或:$a = @(); $a += ...
  • New-Object 的更新 (PS 5.0) 替代方案,我发现它更具可读性(ISE 中提供 Intellisense):一维数组:$array = [int[]]::new(5)。二维数组:$array = [int[][]]::new(5,3)
【解决方案3】:

另一种选择:

for ($i = 0; $i -lt 5; $i++) 
{ 
  $arr += @($false) 
}

如果 $arr 尚未定义,则此方法有效。

注意 - 有更好(且性能更高)的方法可以做到这一点...请参阅下面的 https://stackoverflow.com/a/234060/4570 作为示例。

【讨论】:

  • 这超级慢。由于 .NET 数组无法调整大小,这实际上为新数组分配了额外的空间来存储新项目并复制数据,因此如果在上面的代码 sn-p 中将 $i -lt 5 更改为 $i -lt 500000 您将等待很长时间完成。
  • 我在脱壳时一直使用这种方法。 baaad 习惯于编写必须坚持的脚本时。这是一个完全不必要的 O(n^2) 时间。即使在炮击时,我有时也必须阻止它并以正确的方式进行。不过超级方便。
  • 老实说,我通常根本不使用这种方法,我通常使用ArrayList 或类似的东西。不过,从技术上讲,它并不是一个数组。
  • 这不是正确的做法。由于 PowerShell 处理数组的方式,它恰好可以工作。使用这个:$arr = New-Object bool[] 5
【解决方案4】:

原始示例返回错误,因为数组创建为空,然后您尝试访问第 n 个元素为其分配一个值。

这里有许多创造性的答案,很多我在阅读这篇文章之前并不知道。对于小型阵列来说,一切都很好,但正如 n0rd 指出的那样,性能存在显着差异。

在这里,我使用 Measure-Command 来了解每次初始化需要多长时间。正如您可能猜到的那样,任何使用显式 PowerShell 循环的方法都比使用 .Net 构造函数或 PowerShell 运算符(将在 IL 或本机代码中编译)的方法慢。

总结

  • New-Object@(somevalue)*n 很快(100k 个元素大约 20k 个滴答声)。
  • 使用范围运算符 n..m 创建数组的速度要慢 10 倍(200k 滴答声)。
  • 使用带有Add() 方法的ArrayList 比基线(20M 滴答声)慢1000 倍,就像使用for()ForEach-Object(又名foreach,%)循环一个已经大小的数组一样.
  • 附加+= 是最差的(仅 1000 个元素的 2M 滴答声)。

总的来说,我会说 array*n 是“最好的”,因为:

  • 速度很快。
  • 您可以使用任何值,而不仅仅是类型的默认值。
  • 您可以创建重复值(为了说明,请在 powershell 提示符下键入:(1..10)*10 -join " "('one',2,3)*3
  • 语法简洁。

唯一的缺点:

  • 不明显。如果你以前没有见过这个结构,那么它的作用就不清楚了。

但请记住,在许多情况下,您希望将数组元素初始化为某个值,那么强类型数组正是您所需要的。如果您将所有内容初始化为$false,那么该数组是否会保存$false$true 以外的任何内容?如果不是,那么New-Object type[] n 是“最佳”方法。

测试

创建一个默认数组并设置其大小,然后赋值:

PS> Measure-Command -Expression {$a = new-object object[] 100000} | Format-List -Property "Ticks"
Ticks : 20039

PS> Measure-Command -Expression {for($i=0; $i -lt $a.Length;$i++) {$a[$i] = $false}} | Format-List -Property "Ticks"
Ticks : 28866028

创建布尔数组比对象数组慢一点:

PS> Measure-Command -Expression {$a = New-Object bool[] 100000} | Format-List -Property "Ticks"
Ticks : 130968

它的作用并不明显,New-Object 的文档只是说第二个参数是传递给 .Net 对象构造函数的参数列表。在数组的情况下,参数显然是所需的大小。

用 += 附加

PS> $a=@()
PS> Measure-Command -Expression { for ($i=0; $i -lt 100000; $i++) {$a+=$false} } | Format-List -Property "Ticks"

我厌倦了等待它完成,所以 ctrl+c 然后:

PS> $a=@()
PS> Measure-Command -Expression { for ($i=0; $i -lt    100; $i++) {$a+=$false} } | Format-List -Property "Ticks"
Ticks : 147663
PS> $a=@()
PS> Measure-Command -Expression { for ($i=0; $i -lt   1000; $i++) {$a+=$false} } | Format-List -Property "Ticks"
Ticks : 2194398

正如 (6 * 3) 在概念上类似于 (6 + 6 + 6),所以 ($somearray * 3) 应该给出与 ($somearray + $somearray + $somearray) 相同的结果。但是对于数组,+ 是连​​接而不是加法。

如果 $array+=$element 很慢,您可能会认为 $array*$n 也会很慢,但事实并非如此:

PS> Measure-Command -Expression { $a = @($false) * 100000 } | Format-List -Property "Ticks"
Ticks : 20131

就像Java有一个StringBuilder类来避免在追加时创建多个对象,所以PowerShell似乎有一个ArrayList。

PS> $al = New-Object System.Collections.ArrayList
PS> Measure-Command -Expression { for($i=0; $i -lt 1000; $i++) {$al.Add($false)} } | Format-List -Property "Ticks"
Ticks : 447133
PS> $al = New-Object System.Collections.ArrayList
PS> Measure-Command -Expression { for($i=0; $i -lt 10000; $i++) {$al.Add($false)} } | Format-List -Property "Ticks"
Ticks : 2097498
PS> $al = New-Object System.Collections.ArrayList
PS> Measure-Command -Expression { for($i=0; $i -lt 100000; $i++) {$al.Add($false)} } | Format-List -Property "Ticks"
Ticks : 19866894

范围运算符和Where-Object循环:

PS> Measure-Command -Expression { $a = 1..100000 } | Format-List -Property "Ticks"
Ticks : 239863
Measure-Command -Expression { $a | % {$false} } | Format-List -Property "Ticks"
Ticks : 102298091

注意事项:

  • 我在每次运行之间清空了变量 ($a=$null)。
  • 测试是在配备 Atom 处理器的平板电脑上进行的;您可能会在其他机器上看到更快的速度。 [编辑:在台式机上速度大约是台式机的两倍。]
  • 当我尝试多次运行时,出现了相当大的变化。寻找数量级而不是确切的数字。
  • 在 Windows 8 中使用 PowerShell 3.0 进行测试。

致谢

感谢@halr9000 提供array*n,感谢@Scott Saad 和Lee Desmond 提供New-Object,感谢@EBGreen 提供ArrayList。

感谢@n0rd 让我思考性能问题。

【讨论】:

  • 真棒故障。我有一个很好的脚本,它使用 $_.split(' ')[0..1] 来检查 IIS 日志中的日期和时间,但是当处理时间呈指数增长查看 100,000 个日志时,我改为使用 $_.indexof 方法条目。
【解决方案5】:
$array = 1..5 | foreach { $false }

【讨论】:

  • 我喜欢这个,我在 foreach 的位置放了一个 %,它给了它一个非常严格的初始化。
【解决方案6】:
$array = @()
for($i=0; $i -lt 5; $i++)
{
    $array += $i
}

【讨论】:

    【解决方案7】:

    这是另一个想法。您必须记住,它下面是 .NET:

    $arr = [System.Array]::CreateInstance([System.Object], 5)
    $arr.GetType()
    $arr.Length
    
    $arr = [Object[]]::new(5)
    $arr.GetType()
    $arr.Length
    

    结果:

    IsPublic IsSerial Name                                     BaseType                                                                                               
    -------- -------- ----                                     --------                                                                                               
    True     True     Object[]                                 System.Array                                                                                           
    5
    True     True     Object[]                                 System.Array                                                                                           
    5
    

    使用new() 有一个明显的优势:当您在 ISE 中编程并想要创建一个对象时,ISE 会为您提供所有参数组合及其类型的提示。 New-Object 没有,您必须记住参数的类型和顺序。

    【讨论】:

    • ::new() 确实很方便;值得一提的是它需要 PSv5+。
    【解决方案8】:

    我找到的解决方案是使用 New-Object cmdlet 来初始化适当大小的数组。

    $array = new-object object[] 5 
    for($i=0; $i -lt $array.Length;$i++)
    {
        $array[$i] = $FALSE
    }
    

    【讨论】:

      【解决方案9】:

      如果我事先不知道大小,我会使用数组列表而不是数组。

      $al = New-Object System.Collections.ArrayList
      for($i=0; $i -lt 5; $i++)
      {
          $al.Add($i)
      }
      

      【讨论】:

      • 为什么使用这个而不是数组和 += ?
      • 没有特别的原因。只是来自更严格的语言的习惯,需要预先确定数组大小的大小。另外,我喜欢数组列表中内置的额外功能。
      • 另外,如果你注意到,我也提供了一个数组 += 答案。这一切都是在 3 年前完成的,当时 SO 的工作方式还没有真正定义。今天我会把这两种方法放在一个答案中。
      • @RyanFisher 数组无法调整大小,因此每次调用 += 时,使用 += 都会制作整个数组的完整副本。这意味着 += 是 O(n),而 ArrayList.Add() 是 O(1)。根据我的经验,如果你在远程处理数组,你最好使用 ArrayList。
      【解决方案10】:

      这是另一种典型的方式:

      $array = for($i = 0; $i -le 4; $i++) { $false }
      

      【讨论】:

        【解决方案11】:

        或者试试这个想法。适用于 powershell 5.0+。

        [bool[]]$tf=((,$False)*5)
        

        【讨论】:

        • 用 100000 个元素而不是 5 个元素测量此命令会产生 526247 个滴答声。测量 @(false) * 100000 产生了 91802 个滴答声。
        【解决方案12】:
        $array = foreach($i in 1..5) { $false }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-10-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-06-03
          相关资源
          最近更新 更多