Skip to content

Latest commit

 

History

History
583 lines (376 loc) · 16 KB

hashes.pod

File metadata and controls

583 lines (376 loc) · 16 KB

哈希

哈希 是 Perl 数据结构中的一等公民,它将字符串键和标量值之间一一联系起来。 在其他编程语言中,它们可能被称为 表格关联数组字典 或是 映射。 正如变量名和一个存储位置相对应,哈希中的一个键对应一个值。

一个备受推崇但老旧的说法,便是将哈希比方成电话簿:你可以按朋友的名字来查找 她的电话号码。

哈希有两个重要的属性。第一,它们将一个标量以唯一的键存储。第二,它们不提供特定 的键顺序。哈希就是一个大尺寸键值对容器。

声明哈希

哈希使用 % 印记。按如下方式声明一个词法哈希:

哈希初始为空,没有键也没有值。在布尔上下文中,不含键的哈希求值得假。除此之外, 它返回一个求值为真的字符串。

你可以对哈希的每一个独立的元素进行赋值和访问:

当访问独立元素时,哈希将使用标量印记 $ 且将大括号 { } 用于字符串键。

你可以在单一的表达式内将一个键值对列表赋值给一个哈希:

如果你将奇数个元素赋值给一个哈希,你将收到警告说得不到预想的结果。通常 用 胖逗号 操作符来关联键和值会更加明显,因为它能使“成对”的需求更加突出。 请比较:

……和:

胖逗号操作符表现得和常规逗号一样,但是它使得 Perl 词法分析器将其前的裸字(barewords) 作加了引号般对待。编译命令 strict 不会对这些裸字发出警告,并且如果你有一个和此哈希键 同名的函数,胖逗号 会调用此函数:

哈希的键是 name 而不是 Leonardo。如果你想调用该函数以得到键,请明确地使用函数调用 来消除歧义:

要清空一个哈希,可以将空列表赋值给它 一元 undef 也可以,但相对比较少用:

哈希下标

由于哈希是一个集合,你可以通过下标操作访问其中独立的值。把键用作下标(按键访问 操作) 可以从一个哈希中取得对应的值:

在这个例子中,$name 包含用作哈希键的字符串。和访问数组中的单个元素一样,按照键访问一个 标量值时哈希的印记也会从 % 改为 $

你也可以将字符串字面值用作哈希的键。Perl 会按胖逗号的规则自动为裸字加上引号:

你也许会发现加上引号的哈希字符串字面值键会比较清晰,但是自动加引号的行为已经在 Perl 5 文化中根深蒂固,因此最好将引号留给一些“词不达意”的特殊情况。

甚至连 Perl 5 的关键字也会做自动加引号的处理:

一元加号(unary_coercionsnumeric_operators)使得原本将成为裸字的 shift 跳过自动加引号 行为而变成表达式。隐含的意思是,你可以使用任意表达式────不仅是函数调用────作为哈希的键:

任何求值为字符串的事物都是一个可接受的哈希键。当然,哈希键只能是字符串,如果你使用 某对象作为哈希键,你将得到对象字符串化后的版本而非对象本身:

哈希键的存在性

exists 操作符返回布尔值,指示某哈希是否含有给定的键:

不直接访问哈希键而使用 exists 避免了两个问题。第一,它不对哈希的布尔本质 做检查:一个哈希键可能对应到一个求值得布尔假的值(包括 undef):

第二,在处理嵌套数据结构时,exists 避免值的自生现象(autovivification)。

哈希键对应的操作是 defined。如果一个哈希键存在,它对应是值可能是 undef。 你可以用 defined 检查这个值:

访问哈希的键和值

哈希是集合变量,但是它的行为和数组不太一样。尤其是,你可以迭代一个哈希的所有键和值 或者是键值对。keys 操作符返回由哈希键组成的列表:

values 操作符返回由哈希的值组成的列表:

each 操作符返回一个列表,由一个个键值二元列表组成:

不像数组,哈希没有明确的键值列表排序方式。其顺序依赖于哈希的内部实现,实现又依赖 于你使用的特定 Perl 版本、哈希的尺寸以及一个随机的因素。遵循上述条件,哈希中元素 的顺序对于 keysvalueseach 来说是一致的。修改哈希可能改变这个顺序, 但只要哈希不改变,你可以依赖此顺序。

每一个哈希对于 each 操作符来说只有 单一 的迭代器。通过 each 对哈希进行多 次迭代是不可靠的,如果你在一次迭代过程中开始另一次迭代,前者将过早地结束而后者将 从半路开始。

在空上下文中使用 keysvalues 可以重置一个哈希的迭代器:

你也应该确保不调用尝试使用 each 来迭代哈希的函数。

哈希分片

和数组一样,你也可以在一个操作内访问一列哈希元素。哈希分片 就是一个由哈希键值对组成 的列表。最简单的解释就是使用无序列表初始化多个哈希元素:

这和下列初始化是等价的:

……除了上例中的哈希分片初始化不会 替换 哈希的原有内容。

你可以用分片一次性从哈希中取出多个值:

正如数组分片,哈希印记的改变反映了列表上下文。通过使用大括号进行按键访问, 你仍然可以得知 %addresses 的哈希本质没有变。

哈希分片可以使合并两个哈希变得容易:

这样做和手动对 %canada_addresses 的内容进行循环等价,但是更为短小。

空哈希

一个空哈希不包含键和值。它在布尔上下文中得假。含有至少一个键值对的哈希在布尔 上下文得真,哪怕其所有的键或值或两者同时在布尔上下文中求值得假。

在标量上下文中,对哈希求值得到的是一个字符串,表示已用哈希桶比上已分配哈希桶。 这个字符串并不怎么有用,因为它仅仅表示有关哈希的内部细节且对于 Perl 程序来说 基本没有意义。你可以安全地忽略它。

在列表上下文中,对哈希求值得到类似从 each 操作符取得的键值对列表。然而,你 不能 按迭代产生自 each 的列表的方式迭代此列表,因为这将无限循环,除非此 哈希为空。

哈希惯用语

哈希有若干用途,诸如查找列表或数组中唯一的元素。因为每个键只在哈希中存在一份, 对哈希中相同的键赋值多次仅存储最近的值:

对哈希分片使用 undef 操作符可以将哈希的值设置为 undef。这是判定某元素 是否存在于集合中成本最低的方法。

在对元素计数时哈希也很有用,比如在日志文件中的一列 IP 地址:

哈希值初始为 undef。后缀自增操作符(++)将其作为零对待。这个对值即时修改 增加某个键对应的值。如果该键对应的值不存在,它创建一个值(undef)并立刻将其 加一,因为数值化的 undef 产生值 0。

此策略的一个变种很适合缓存,即你愿意付出一点存取代价来存放某昂贵计算的结果:

如果已经存在,这个 Orcish Maneuver “Or-cache”,如果你喜欢双关语的话 会 从哈希中返回值。否则,计算该值,存入缓存,再返回它。注意布尔或赋值操作符(||=) 作用于布尔值之上,如果你的缓存值在布尔上下文中求值得假,则可以使用“已定义-或” 赋值操作符(//=)来代替:

这个惰性 Orcish Maneuver 检查缓存值是否已被定义,而非其布尔真假。“已定义-或”赋值 操作符是 Perl 5.10 中的新功能。

哈希也可以收集传递给函数的具名参数。如果你的函数接受若干参数,你可以使用吸入式哈 希(parameter_slurping)来把键值对收集在单个哈希中:

你甚至用如下方式设置默认参数:

……或者将它们包含在最初的声明和赋值中:

……因为后续对同一键的不同值声明会覆盖前面的值。

哈希上锁

哈希的一个缺点就是它们的键是几乎不提供打字错误保护的裸字(特别是将其和受 strict 编译命令保护的函数、变量名相比)。核心模块 Hash::Util 提供了一些机制来对哈希的 修改和允许的键做出限制。

为避免他人向哈希添加你不想要的键(假设一个打字错误或是不受信任的输入),你可以使用 lock_keys() 函数将哈希键限制在当前集合中。任何添加不被允许的键值对的意图将引发 一个异常。

当然,其他想达到此目的的人总是可以使用 unlock_keys() 函数来去掉保护,因此不要 将此作为防止其他程序员误用的安全保障来信任。

类似的你可以对哈希中给定的键对应已存在的值进行上锁和解锁(lock_value()unlock_value()) 以及利用 lock_hash()unlock_hash() 使得或不使整个哈希变为只读。

POD ERRORS

Hey! The above document had some coding errors, which are explained below:

Around line 5:

A non-empty Z<>

Around line 118:

Deleting unknown formatting code N<>

Around line 492:

Deleting unknown formatting code N<>

Around line 565:

A non-empty Z<>