我是Perl脚本新手,对foreach
的哈希变量有疑问。我想打印哈希值的所有值。这是一个程序:
%colors = (a => 1, b=>2, c=>3, d=>4, e=>5);
foreach $colors(keys %colors)
{
print "$colors{%colors} \n";
}
输出结果为:
5
3
1
2
4
为什么值会随机排序?或者这种随机性背后的逻辑是什么?请澄清我的疑问。
答案 0 :(得分:8)
我认为你的困惑在于不确切知道Hash 是什么。大多数语言都有类似于键值存储的东西,在Ruby和Perl中它们被称为Hashes,在Java地图中,在Python字典中等等......
它们本质上是一回事,你将一个带有唯一键的值插入到某个底层数据结构中,以便以内存为代价直接访问它。
那么在向哈希添加密钥和值时会发生什么?
哈希是围绕 hash functions 的概念构建的,当输入计算一个唯一的输出时(理想情况下每个输入都有自己独特的输出),它会取一些值。如果两个输入都映射到同一输出,则称为 collision 。
现在我们需要讨论如何实现Hash,两个经典示例是单个数组或链接列表数组。我将在下面展示数组示例。
阵列
在简单数组的情况下,Hash底层的数据结构只是一个大小的数组。散列函数用于计算该数组的索引。如果我们假设一个简单的哈希算法
h(x) = length(x) % ARRAY_SIZE
此处x
是一个字符串,ARRAY_SIZE
是我们的基础数组的大小,此语句将确保所有值x
都落在0..ARRAY_SIZE - 1
<范围内/ p>
要查看可视示例,请考虑大小为5的数组:
0 1 2 3 4
------------------------------
| | | | | |
------------------------------
并假设我们正在尝试使用键5
存储值abcd
,根据我们的哈希算法
h('abcd') = length('abcd') % ARRAY_SIZE
= 4 % 5
= 4
因此值5
将存储在索引4
:
0 1 2 3 4
------------------------------
| | | | | 5 |
------------------------------
现在如果我们尝试使用密钥3
存储值dcba
会发生什么情况,这两个密钥是不同的?他们应该映射到不同的地方。
h('dcba') = length('dcba') % ARRAY_SIZE
= 4 % 5
= 4
糟糕!这个键也映射到索引4
,那么我们现在要做什么呢?好吧,我们不能只丢掉键值对,因为程序员显然需要/想要在Hash中进行这种配对,所以我们需要决定在发生碰撞时该怎么做。有许多算法可以做到这一点,但最简单的算法是查找数组中的下一个打开的插槽并存储它们的3
。所以现在我们的数组看起来像:
0 1 2 3 4
------------------------------
| 3 | | | | 5 |
------------------------------
这不是一个非常深入的解释,但希望它能让我们深入了解为什么从Hashes中检索值似乎是 random ,因为基础数据结构会不断变化,如果你要问的话对于你现在的Hash中的键,你可能会回来(3, 5)
,即使你先插入5
,只是因为3
首先出现在数组中。
希望这有用。
答案 1 :(得分:7)
哈希是由关联字符串键索引的标量值的无序集合。
您可以sort
这些键,或者,如果您想保留初始化中给出的顺序,请使用Tie::Hash::Indexed或Tie::IxHash。
答案 2 :(得分:3)
perldoc中keys
的说明包含以下代码段:
哈希条目以明显随机的顺序返回。实际的随机顺序特定于给定的哈希值;两个哈希值上完全相同的一系列操作可能会导致每个哈希值的顺序不同。任何插入哈希都可以改变顺序,任何删除也是如此,除了可以删除每个或多个键返回的最新密钥而不改变顺序。只要给定的散列未经修改,您可以依赖于键,值和每个重复返回相同的顺序。有关哈希顺序随机化的原因的详细信息,请参阅Algorithmic Complexity Attacks in perlsec。除了这里提供的保证之外,Perl的散列算法和散列遍历顺序的确切细节在任何Perl版本中都会发生变化。
Perlsec说明了关于哈希算法的内容:
哈希算法 - 众所周知,像Perl中使用的哈希算法容易受到哈希函数的冲突攻击。这种攻击涉及构造一组密钥,这些密钥碰撞到同一个桶中,从而产生低效的行为。此类攻击通常依赖于发现用于将密钥映射到桶的散列函数的种子。然后,该种子用于暴力破解可用于发起拒绝服务攻击的密钥集。在Perl 5.8.1中引入了更改来强化Perl到这种攻击,然后在Perl 5.18.0中,这些功能得到了增强,并增加了额外的保护。 在撰写本文时,Perl 5.18.0被认为能够很好地抵御其哈希实现的算法复杂性攻击。这主要归功于以下措施减轻攻击:
哈希种子随机化
为了不可能知道生成攻击密钥集的种子,该种子在进程开始时被随机初始化。这可以通过使用PERL_HASH_SEED环境变量来覆盖,请参阅perlrun中的PERL_HASH_SEED。此环境变量控制项目的实际存储方式,而不是如何通过键,值和每个显示项目。哈希遍历随机化
与哈希函数中使用的种子无关,键,值和每个以每哈希随机顺序返回项目。通过插入修改哈希将更改该哈希的迭代顺序。可以使用Hash :: Util中的hash_traversal_mask()或使用PERL_PERTURB_KEYS环境变量来覆盖此行为,请参阅perlrun中的PERL_PERTURB_KEYS。请注意,此功能可控制&#34;可见&#34;键的顺序,而不是它们存储的实际顺序。- 铲斗顺序扰动
当项目碰撞到给定的哈希桶时,它们存储在链中的顺序在Perl 5.18中不再可预测。这样做的目的是使观察碰撞更加困难。可以使用PERL_PERTURB_KEYS环境变量覆盖此行为,请参阅perlrun中的PERL_PERTURB_KEYS。- 新的默认哈希函数
默认的哈希函数已被修改,目的是使得更难推断哈希种子。- 替代哈希函数
源代码包括多个可供选择的哈希算法。虽然我们认为默认的perl哈希对攻击很有效,但我们已经将哈希函数Siphash作为后备选项包含在内。在发布Perl 5.18.0时,Siphash被认为具有加密强度。这不是默认值,因为它比默认哈希慢得多。如果不编译特殊的Perl,就无法获得Perl 5.18.0之前任何版本的完全相同的行为。最接近的是将PERL_PERTURB_KEYS设置为0并将PERL_HASH_SEED设置为已知值。出于上述安全考虑,我们不建议将这些设置用于生产用途。
Perl从未保证任何散列键的排序,并且在Perl 5的生命周期中排序已经多次改变。此外,散列键的排序一直并且继续受插入顺序的影响以及在生命周期内对哈希所做的更改的历史。
另请注意,虽然哈希元素的顺序可能是随机的,但这个&#34;伪顺序&#34;应该不用于随机混乱列表等应用程序(使用List :: Util :: shuffle(),参见List :: Util,自Perl 5.8.0以来的标准核心模块;或者CPAN模块Algorithm :: Numerical :: Shuffle),或用于生成排列(使用例如CPAN模块Algorithm :: Permute或Algorithm :: FastPermute),或用于任何加密应用程序。
答案 3 :(得分:2)
你可以使用sort来按字母顺序打印出结果(因为你的密钥是字母数字)排序方式如下:
%colors = ("a" => 1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);
foreach (sort keys %colors) {
print $colors{$_} . "\n";
}
或者,如果您希望按值排序:
%colors = ("a" => 1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);
foreach (sort { $colors{$a} <=> $colors{$b} } keys %colors) {
print $colors{$_} . "\n";
}