哈希 是 Perl 数据结构中的一等公民,它将字符串键和标量值之间一一联系起来。 在其他编程语言中,它们可能被称为 表格、关联数组、字典 或是 映射。 正如变量名和一个存储位置相对应,哈希中的一个键对应一个值。
一个备受推崇但老旧的说法,便是将哈希比方成电话簿:你可以按朋友的名字来查找 她的电话号码。
哈希有两个重要的属性。第一,它们将一个标量以唯一的键存储。第二,它们不提供特定 的键顺序。哈希就是一个大尺寸键值对容器。
哈希使用 %
印记。按如下方式声明一个词法哈希:
哈希初始为空,没有键也没有值。在布尔上下文中,不含键的哈希求值得假。除此之外, 它返回一个求值为真的字符串。
你可以对哈希的每一个独立的元素进行赋值和访问:
当访问独立元素时,哈希将使用标量印记 $
且将大括号 { }
用于字符串键。
你可以在单一的表达式内将一个键值对列表赋值给一个哈希:
如果你将奇数个元素赋值给一个哈希,你将收到警告说得不到预想的结果。通常 用 胖逗号 操作符来关联键和值会更加明显,因为它能使“成对”的需求更加突出。 请比较:
……和:
胖逗号操作符表现得和常规逗号一样,但是它使得 Perl 词法分析器将其前的裸字(barewords) 作加了引号般对待。编译命令 strict
不会对这些裸字发出警告,并且如果你有一个和此哈希键 同名的函数,胖逗号 不 会调用此函数:
哈希的键是 name
而不是 Leonardo
。如果你想调用该函数以得到键,请明确地使用函数调用 来消除歧义:
要清空一个哈希,可以将空列表赋值给它 一元 undef
也可以,但相对比较少用:
由于哈希是一个集合,你可以通过下标操作访问其中独立的值。把键用作下标(按键访问 操作) 可以从一个哈希中取得对应的值:
在这个例子中,$name
包含用作哈希键的字符串。和访问数组中的单个元素一样,按照键访问一个 标量值时哈希的印记也会从 %
改为 $
。
你也可以将字符串字面值用作哈希的键。Perl 会按胖逗号的规则自动为裸字加上引号:
你也许会发现加上引号的哈希字符串字面值键会比较清晰,但是自动加引号的行为已经在 Perl 5 文化中根深蒂固,因此最好将引号留给一些“词不达意”的特殊情况。
甚至连 Perl 5 的关键字也会做自动加引号的处理:
一元加号(unary_coercions、numeric_operators)使得原本将成为裸字的 shift
跳过自动加引号 行为而变成表达式。隐含的意思是,你可以使用任意表达式────不仅是函数调用────作为哈希的键:
任何求值为字符串的事物都是一个可接受的哈希键。当然,哈希键只能是字符串,如果你使用 某对象作为哈希键,你将得到对象字符串化后的版本而非对象本身:
exists
操作符返回布尔值,指示某哈希是否含有给定的键:
不直接访问哈希键而使用 exists
避免了两个问题。第一,它不对哈希值的布尔本质 做检查:一个哈希键可能对应到一个求值得布尔假的值(包括 undef
):
第二,在处理嵌套数据结构时,exists
避免值的自生现象(autovivification)。
哈希键对应的操作是 defined
。如果一个哈希键存在,它对应是值可能是 undef
。 你可以用 defined
检查这个值:
哈希是集合变量,但是它的行为和数组不太一样。尤其是,你可以迭代一个哈希的所有键和值 或者是键值对。keys
操作符返回由哈希键组成的列表:
values
操作符返回由哈希的值组成的列表:
each
操作符返回一个列表,由一个个键值二元列表组成:
不像数组,哈希没有明确的键值列表排序方式。其顺序依赖于哈希的内部实现,实现又依赖 于你使用的特定 Perl 版本、哈希的尺寸以及一个随机的因素。遵循上述条件,哈希中元素 的顺序对于 keys
、values
和 each
来说是一致的。修改哈希可能改变这个顺序, 但只要哈希不改变,你可以依赖此顺序。
每一个哈希对于 each
操作符来说只有 单一 的迭代器。通过 each
对哈希进行多 次迭代是不可靠的,如果你在一次迭代过程中开始另一次迭代,前者将过早地结束而后者将 从半路开始。
在空上下文中使用 keys
或 values
可以重置一个哈希的迭代器:
你也应该确保不调用尝试使用 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()
使得或不使整个哈希变为只读。
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<>