内容

名称

Hash::Util - 一系列通用实用哈希子例程

概要

# Restricted hashes

use Hash::Util qw(
                   fieldhash fieldhashes

                   all_keys
                   lock_keys unlock_keys
                   lock_value unlock_value
                   lock_hash unlock_hash
                   lock_keys_plus
                   hash_locked hash_unlocked
                   hashref_locked hashref_unlocked
                   hidden_keys legal_keys

                   lock_ref_keys unlock_ref_keys
                   lock_ref_value unlock_ref_value
                   lock_hashref unlock_hashref
                   lock_ref_keys_plus
                   hidden_ref_keys legal_ref_keys

                   hash_seed hash_value hv_store
                   bucket_stats bucket_info bucket_array
                   lock_hash_recurse unlock_hash_recurse
                   lock_hashref_recurse unlock_hashref_recurse

                   hash_traversal_mask
                 );

my %hash = (foo => 42, bar => 23);
# Ways to restrict a hash
lock_keys(%hash);
lock_keys(%hash, @keyset);
lock_keys_plus(%hash, @additional_keys);

# Ways to inspect the properties of a restricted hash
my @legal = legal_keys(%hash);
my @hidden = hidden_keys(%hash);
my $ref = all_keys(%hash,@keys,@hidden);
my $is_locked = hash_locked(%hash);

# Remove restrictions on the hash
unlock_keys(%hash);

# Lock individual values in a hash
lock_value  (%hash, 'foo');
unlock_value(%hash, 'foo');

# Ways to change the restrictions on both keys and values
lock_hash  (%hash);
unlock_hash(%hash);

my $hashes_are_randomised = hash_seed() !~ /^\0+$/;

my $int_hash_value = hash_value( 'string' );

my $mask= hash_traversal_mask(%hash);

hash_traversal_mask(%hash,1234);

说明

Hash::UtilHash::Util::FieldHash 包含一些特殊函数,用于处理那些实际上并不需要关键字的哈希。

Hash::Util 包含一组支持 受限哈希 的函数。本文件中对此进行了说明。Hash::Util::FieldHash 包含一组(不相关的)函数,用于支持在反向类中使用哈希,如 Hash::Util::FieldHash 中所述。

默认情况下,Hash::Util 不导出任何内容。

受限哈希

5.8.0 引入了将哈希限制为特定密钥集的功能。该集之外的任何密钥都无法添加。它还引入了锁定单个密钥的功能,以便无法删除该密钥,以及确保无法更改单个值的功能。

这旨在很大程度上取代已弃用的伪哈希。

lock_keys
unlock_keys
lock_keys(%hash);
lock_keys(%hash, @keys);

将给定 %hash 的键集限制为 @keys。如果未给出 @keys,则将其限制为其当前键集。不能再添加更多键。delete() 和 exists() 仍将起作用,但不会更改允许的键集。注意:当前实现阻止在锁定状态下对哈希进行 bless()。任何尝试这样做都会引发异常。当然,你仍可以在调用 lock_keys() 之前对哈希进行 bless(),因此这应该不是问题。

unlock_keys(%hash);

移除对 %hash 键集的限制。

注意,如果哈希的任何值已被锁定,则在该子执行后它们将不会被解锁。

这两个例程都返回对被操作的哈希的引用。

lock_keys_plus
lock_keys_plus(%hash,@additional_keys)

类似于 lock_keys(),不同之处在于可选键列表指定可能已经在哈希中也可能不在哈希中的键。从本质上讲,这是一个更简单的方法来说

lock_keys(%hash,@additional_keys,keys %hash);

返回对 %hash 的引用

lock_value
unlock_value
lock_value  (%hash, $key);
unlock_value(%hash, $key);

锁定和解锁哈希的单个键的值。锁定键的值不能被更改。

除非 %hash 已被锁定,否则无论此设置如何,键/值都可能被删除。

返回对 %hash 的引用。

lock_hash
unlock_hash
lock_hash(%hash);

lock_hash() 锁定整个哈希,使所有键和值都变为只读。不能更改任何值,不能添加或删除任何键。

unlock_hash(%hash);

unlock_hash() 执行与 lock_hash() 相反的操作。所有键和值都变为可写。所有值都可以更改,并且可以添加和删除键。

返回对 %hash 的引用。

lock_hash_recurse
unlock_hash_recurse
lock_hash_recurse(%hash);

lock_hash() 锁定整个哈希及其引用的任何哈希(递归),使所有键和值都变为只读。不能更改任何值,不能添加或删除任何键。

此方法递归到由另一个哈希引用的哈希中。因此,哈希的哈希 (HoH) 将全部受到限制,但哈希的哈希数组 (HoAoH) 将仅限制顶级哈希。

unlock_hash_recurse(%hash);

unlock_hash_recurse() 执行与 lock_hash_recurse() 相反的操作。所有键和值都变为可写。所有值都可以更改,并且可以添加和删除键。与 lock_hash_recurse() 相同的递归限制也适用。

返回对 %hash 的引用。

hashref_locked
hash_locked
hashref_locked(\%hash) and print "Hash is locked!\n";
hash_locked(%hash) and print "Hash is locked!\n";

如果哈希及其键被锁定,则返回 true。

hashref_unlocked
hash_unlocked
hashref_unlocked(\%hash) and print "Hash is unlocked!\n";
hash_unlocked(%hash) and print "Hash is unlocked!\n";

如果哈希及其键被解锁,则返回 true。

my @keys = legal_keys(%hash);

返回受限哈希中合法的键列表。对于不受限的哈希,这与调用 keys(%hash) 相同。

hidden_keys
my @keys = hidden_keys(%hash);

返回受限哈希中合法但没有关联值的键列表。因此,如果“foo”是 %hash 的“隐藏”键,它将对 definedexists 测试都返回 false。

对于不受限的哈希,这将返回一个空列表。

注意这是一个实验性功能,严重依赖于受限哈希的当前实现。如果实现发生变化,此例程可能变得毫无意义,在这种情况下,它将返回一个空列表。

all_keys
all_keys(%hash,@keys,@hidden);

使用所有将通过 exists 测试的键填充数组 @keys,并使用尚未使用的剩余合法键填充 @hidden。

返回对哈希的引用。

对于不受限的哈希,这将等效于

$ref = do {
    @keys = keys %hash;
    @hidden = ();
    \%hash
};

注意这是一个实验性功能,严重依赖于受限哈希的当前实现。如果实现发生变化,此例程可能变得毫无意义,在这种情况下,它将与在不受限哈希上表现的方式相同。

hash_seed
my $hash_seed = hash_seed();

hash_seed() 返回用于随机化哈希顺序的种子字节。

请注意,哈希种子是敏感信息:通过了解它,即使远程也可以对 Perl 代码实施拒绝服务攻击,有关更多信息,请参阅 perlsec 中的“算法复杂度攻击”不要向不需要了解它的人透露哈希种子。另请参阅 perlrun 中的“PERL_HASH_SEED_DEBUG”

在 Perl 5.17.6 之前,此函数返回一个 UV,现在它返回一个字符串,其大小可能几乎是任意大小,由 Perl 构建时的哈希函数决定。可能的大小包括但不限于 4 字节(对于大多数哈希算法)和 16 字节(对于 siphash)。

hash_value
my $hash_value = hash_value($string);
my $hash_value = hash_value($string, $seed);

hash_value($string) 返回给定字符串的当前 perl 内部哈希值。hash_value($string, $seed) 返回使用不同种子计算的哈希值。如果自定义种子太短,则函数会出错。种子的最小长度取决于实现。

返回一个 32 位整数,表示传入字符串的哈希值。1 参数值仅在进程的生命周期内可靠。它可能因调用、环境变量、perl 版本、架构和构建选项而异。

请注意,给定字符串的哈希值是敏感信息:通过了解它,可以推导出哈希种子,进而可以远程制作对 Perl 代码的拒绝服务攻击,有关详细信息,请参阅 perlsec 中的 "Algorithmic Complexity Attacks"不要向不需要知道的人透露字符串的哈希值。另请参阅 perlrun 中的 "PERL_HASH_SEED_DEBUG"

bucket_info

返回有关哈希的一组基本信息。

my ($keys, $buckets, $used, @length_counts)= bucket_info($hash);

字段如下

0: Number of keys in the hash
1: Number of buckets in the hash
2: Number of used buckets in the hash
rest : list of counts, Kth element is the number of buckets
       with K keys in it.

另请参阅 bucket_stats() 和 bucket_array()。

bucket_stats

返回有关哈希的一组统计信息。

my ($keys, $buckets, $used, $quality, $utilization_ratio,
       $collision_pct, $mean, $stddev, @length_counts)
   = bucket_stats($hashref);

字段如下

0: Number of keys in the hash
1: Number of buckets in the hash
2: Number of used buckets in the hash
3: Hash Quality Score
4: Percent of buckets used
5: Percent of keys which are in collision
6: Mean bucket length of occupied buckets
7: Standard Deviation of bucket lengths of occupied buckets
rest : list of counts, Kth element is the number of buckets
       with K keys in it.

另请参阅 bucket_info() 和 bucket_array()。

请注意,理想哈希的哈希质量分数为 1,接近或低于 1 的数字表示良好的哈希,而明显高于 1 的数字表示较差的分数。在实践中,它应该在 0.95 到 1.05 之间。它被定义为

$score= sum( $count[$length] * ($length * ($length + 1) / 2) )
           /
           ( ( $keys / 2 * $buckets ) *
             ( $keys + ( 2 * $buckets ) - 1 ) )

该公式来自 Red Dragon 书籍(重新表述为使用可用数据),并在 http://www.strchr.com/hash_functions 中记录

bucket_array
my $array= bucket_array(\%hash);

返回与哈希关联的存储桶数组的打包表示形式。数组的每个元素要么是整数 K,在这种情况下它表示 K 个空存储桶,要么是对包含该存储桶中键的另一个数组的引用。

请注意,bucket_array 返回的信息是敏感信息:通过了解它,可以直接攻击 perl 的哈希函数,进而可以远程制作对 Perl 代码的拒绝服务攻击,有关详细信息,请参阅 perlsec 中的 "Algorithmic Complexity Attacks"不要向不需要知道的人透露此函数的输出。另请参阅 perlrun 中的 "PERL_HASH_SEED_DEBUG"。此函数仅严格用于调试和诊断目的,很难想象在生产代码中使用它的理由。

bucket_stats_formatted
print bucket_stats_formatted($hashref);

返回 bucket_stats() 返回的信息的格式化报告。示例报告如下所示

Keys: 50 Buckets: 33/64 Quality-Score: 1.01 (Good)
Utilized Buckets: 51.56% Optimal: 78.12% Keys In Collision: 34.00%
Chain Length - mean: 1.52 stddev: 0.66
Buckets 64          [0000000000000000000000000000000111111111111111111122222222222333]
Len   0 Pct:  48.44 [###############################]
Len   1 Pct:  29.69 [###################]
Len   2 Pct:  17.19 [###########]
Len   3 Pct:   4.69 [###]
Keys    50          [11111111111111111111111111111111122222222222222333]
Pos   1 Pct:  66.00 [#################################]
Pos   2 Pct:  28.00 [##############]
Pos   3 Pct:   6.00 [###]

第一组统计数据提供了一些汇总统计信息,包括质量分数,转化为“好”、“差”和“坏”,(分数<=1.05,分数<=1.2,分数>1.2)。有关更多详细信息,请参阅 bucket_stats() 中的文档。

两组条形图提供统计数据和哈希性能的可视化指示。

第一个提供有关存储桶链长度的数据,并提供有关获取 *未命中* 将需要多少工作的见解。在这种情况下,我们必须检查存储桶中的每个项目,然后才能确定该项目不在列表中。插入的性能与此情况相当,就像项目不在哈希中的删除一样。

第二个提供有关链中每个深度有多少个键的数据,并提供有关获取 *命中* 将需要多少工作的想法。哈希中项目的更新或删除的性能与此情况相当。

请注意,这些统计信息仅为摘要。实际性能将取决于访问哈希的实际命中/未命中比率。如果您担心命中率,建议您使用类似以下内容“加大”哈希的大小

keys(%hash)= keys(%hash) << $k;

仔细选择 $k,并且很可能是 1 或 2 等小数字。从理论上讲,存储桶数组越大,发生冲突的可能性就越小。

hv_store
my $sv = 0;
hv_store(%hash,$key,$sv) or die "Failed to alias!";
$hash{$key} = 1;
print $sv; # prints 1

将别名存储在哈希中的变量中,而不是复制值。

hash_traversal_mask

从 Perl 5.18 开始,每个哈希都有自己的哈希遍历顺序,并且每次将新元素插入哈希时,此顺序都会更改。此功能通过维护一个无符号整数掩码 (U32) 来提供,该掩码在使用 keys()、values() 或 each() 遍历哈希存储桶期间与实际存储桶 ID 进行异或运算。

您可以使用此子例程获取和设置特定哈希的遍历掩码。设置掩码可确保给定哈希将生成相同的键顺序。请注意,这不能保证两个哈希将为相同的哈希种子和遍历掩码生成相同的键顺序,无论此设置如何,冲突到一个存储桶中的项目可能具有不同的顺序。

bucket_ratio

此函数的行为方式与标量 (%hash) 在 Perl 5.25 之前的方式相同。具体来说,如果哈希被绑定,则它将调用 SCALAR 绑定哈希方法,如果未绑定,则如果哈希为空,它将返回 0,否则它将返回一个字符串,其中包含哈希中已用存储桶的数量,后跟一个斜杠,后跟哈希中存储桶的总数。

my %hash=("foo"=>1);
print Hash::Util::bucket_ratio(%hash); # prints "1/8"
used_buckets

此函数返回哈希中已用存储桶的数量。计算成本较高,且值不会被缓存,因此请避免在生产代码中使用此函数。

num_buckets

此函数返回哈希所包含的存储桶总数,或在创建数组时所包含的存储桶总数。(当哈希刚创建时,即使此值不为零,也可能未分配数组。)

对哈希的引用进行操作。

此模块中记录的大多数子例程都有等效版本,它们对哈希的引用而不是本机哈希进行操作。以下是这些子例程的列表。它们除了名称和不采用 %hash 而采用 $hashref 外,其他方面完全相同,而且没有原型。

lock_ref_keys
unlock_ref_keys
lock_ref_keys_plus
lock_ref_value
unlock_ref_value
lock_hashref
unlock_hashref
lock_hashref_recurse
unlock_hashref_recurse
hash_ref_unlocked
hidden_ref_keys

注意事项

请注意,受限操作的捕获不是原子的:例如

eval { %hash = (illegal_key => 1) }

将使 %hash 为空,而不是保留其原始内容。

错误

此模块公开的接口非常接近受限哈希的当前实现。随着时间的推移,预计此行为将得到扩展,并且接口将进一步抽象化。

作者

Michael G Schwern <[email protected]>,基于 Nick Ing-Simmons 和 Jeffrey Friedl 的代码。

hv_store() 来自 Array::RefElem,版权所有 2000 Gisle Aas。

Yves Orton 的附加代码。

Christopher Yeleighton <[email protected]> 对 hash_value($string, $seed) 的描述

另请参见

Scalar::UtilList::Utilperlsec 中的“算法复杂度攻击”

Hash::Util::FieldHash.