什么是最安全的方式来遍历Perl哈希键?

如果我有一堆(键,值)对的Perl哈希,迭代所有键的首选方法是什么? 我听说each可能有某种意想不到的副作用。 那么,这是真的,是以下两种方法中最好的一种,还是有更好的办法?

 # Method 1 while (my ($key, $value) = each(%hash)) { # Something } # Method 2 foreach my $key (keys(%hash)) { # Something } 

经验法则是使用最适合您需求的function。

如果你只是想要的钥匙,并不打算读取任何值,使用keys():

 foreach my $key (keys %hash) { ... } 

如果你只是想要的值,使用values():

 foreach my $val (values %hash) { ... } 

如果您需要键值,请使用each():

 keys %hash; # reset the internal iterator so a prior each() doesn't affect the loop while(my($k, $v) = each %hash) { ... } 

如果您打算以任何方式更改散列键, 除了在迭代期间删除当前键,则不得使用each()。 例如,这个代码创build一个具有加倍值的新的大写键集合可以很好的使用keys():

 %h = (a => 1, b => 2); foreach my $k (keys %h) { $h{uc $k} = $h{$k} * 2; } 

产生预期的结果散列:

 (a => 1, A => 2, b => 2, B => 4) 

但是使用each()来做同样的事情:

 %h = (a => 1, b => 2); keys %h; while(my($k, $v) = each %h) { $h{uc $k} = $h{$k} * 2; # BAD IDEA! } 

以难以预测的方式产生不正确的结果。 例如:

 (a => 1, A => 2, b => 2, B => 8) 

然而,这是安全的:

 keys %h; while(my($k, $v) = each %h) { if(...) { delete $h{$k}; # This is safe } } 

所有这些在perl文档中都有描述:

 % perldoc -f keys % perldoc -f each 

有一件事你应该知道什么时候使用each是它有一个副作用join“状态”你的散列(散列必须记住“下一个”键是什么)。 当使用像上面发布的代码片段一样遍历整个散列的代码时,这通常不是问题。 但是,在处理完所有密钥之前,您会遇到难以find问题的情况(我从经验上讲);将eachlastreturn这样的语句一起使用,从while ... each循环中退出。

在这种情况下,哈希将会记住它已经返回了哪些键,并且当你下一次使用each (也许在一个完全不相关的代码段)时,它将会继续在这个位置。

例:

 my %hash = ( foo => 1, bar => 2, baz => 3, quux => 4 ); # find key 'baz' while ( my ($k, $v) = each %hash ) { print "found key $k\n"; last if $k eq 'baz'; # found it! } # later ... print "the hash contains:\n"; # iterate over all keys: while ( my ($k, $v) = each %hash ) { print "$k => $v\n"; } 

这打印:

 found key bar found key baz the hash contains: quux => 4 foo => 1 

键“bar”和“baz”发生了什么事?他们仍然在那里,但是第二个从第一个离开的位置开始,在到达散列末尾时停止,所以我们从来没有在第二个循环中看到它们。

each可能导致你问题的地方是它是一个真正的,非范围的迭代器。 举例来说:

 while ( my ($key,$val) = each %a_hash ) { print "$key => $val\n"; last if $val; #exits loop when $val is true } # but "each" hasn't reset!! while ( my ($key,$val) = each %a_hash ) { # continues where the last loop left off print "$key => $val\n"; } 

如果你需要确保each键和值each得到,你需要确保你首先使用keysvalues (因为这会重置迭代器)。 请参阅每个文档 。

使用每种语法将会阻止一次生成整组密钥。 如果您将绑定哈希用于具有数百万行的数据库,这可能很重要。 您不希望一次生成完整的密钥列表,并耗尽您的物理内存。 在这种情况下,每个函数都用作迭代器,而在循环开始之前,键实际上会生成整个数组。

所以,唯一的地方是“每一个”是真正的用途,当散列非常大(与可用内存相比)。 这只有在散列本身不存在于内存本身时才会发生,除非您正在编程手持数据收集设备或具有小内存的东西。

如果内存不是问题,那么通常地图或键范例就是更加优先和更容易阅读的范例。

我总是使用方法2。 使用每一个的唯一好处是如果你只是阅读(而不是重新分配)哈希条目的值,你并不是经常去引用哈希。

关于这个主题的一些其他想法:

  1. 任何哈希迭代器本身都没有任何不安全的地方。 什么是不安全的是修改散列的键,而你迭代它。 (修改这些值是完全安全的。)我能想到的唯一潜在的副作用是values返回别名,这意味着修改它们将修改哈希的内容。 这是devise的,但在某些情况下可能不是你想要的。
  2. John 接受的答案很好,只有一个例外:文档很清楚,在迭代散列时添加键是不安全的。 它可能适用于某些数据集,但依赖于哈希顺序将会失败。
  3. 如前所述,删除each返回的最后一个键是安全的。 对于keys不是真的,因为each keys都是一个迭代器,而keys返回一个列表。

我可能会被这一个咬,但我认为这是个人喜好。 我找不到任何在文档中引用each()不同于keys()或values()(除了明显的“他们返回不同的东西”的答案。实际上文档声明使用相同的迭代器,他们都返回实际的列表值而不是它们的副本,并且在使用任何调用遍历它时修改哈希是不好的。

总而言之,我几乎总是使用keys(),因为对我来说,通过hash本身访问key的值通常是更多的self文档。 当值是一个大型结构的引用时,偶尔使用values(),并且哈希键已经存储在结构中,此时键是多余的,我不需要它。 我认为我已经在10年的Perl编程中使用了each()两次,这可能是错误的select=)

我通常使用keys ,我想不出最后一次使用或读取each使用。

不要忘了map ,这取决于你在循环中做什么!

 map { print "$_ => $hash{$_}\n" } keys %hash; 

我会说:

  1. 对大多数人来说,使用最简单的方法来阅读/理解(通常我会这么认为)
  2. 在整个代码库中使用你所决定的一切。

这给了2个主要优点:

  1. 发现“通用”代码更容易,因此您可以重新考虑function/方法。
  2. 未来的开发者更容易维护。

我不认为在每个键上使用键是比较昂贵的,所以在你的代码中不需要两个不同的构造。