哈希的概念与性质
哈希是Perl中的第三种数据结构,又称关联数组。其与数组具有一定的相似性,许多特性可以与数组形成类比。哈希同数组一样,可以容纳任意多的元素并按需取用。
哈希与数组最大的不同之处在于索引方式。在数组中,数组元素以索引值来索引,而在哈希中,用于索引的是一个任意的字符串,这个字符串就称为哈希键,其对应的值就称为哈希值。所以哈希就是由许多哈希键-值对所组成的一个数据集合。
哈希键具有唯一性,即在一个哈希中,不可能出现两个相同的哈希键。但哈希键所对应的哈希值是可以重复的。
哈希具有无序性,也可以理解为乱序性,这是哈希区别于数组的一大重要特点。在哈希中,不存在哈希键-值对的先后顺序,即不存在“第n个哈希元素”这样的说法,只存在哈希键与哈希值之间的一一对应关系。并且Perl出于特殊需要,还会在某些情况下刻意重排哈希中键-值对的先后顺序。
访问整个哈希与哈希赋值
类似于数组中的@,如果要指代整个哈希,可以使用百分号%作为限定符,后接哈希名称。哈希名称也属于Perl标识符的一种,有其独立的名字空间。哈希与列表可以相互转换,所以对整个哈希赋值就可以通过列表来完成。在给哈希赋值的列表中,列表的第一、二个元素分别是一个哈希键及其对应的哈希值,第三、四个元素分别是下一对哈希键-值对,以此类推。下例为一个对整个哈希赋值的示例程序:
%hash=qw/a aaa b bbb/; #对整个哈希%hash赋值,存入两个键-值对:a对应aaa、b对应bbb
在Perl中,胖箭头“=>”就是逗号的另一种写法,所以在列表中,其可以发挥和逗号一样的作用,即分隔多个列表元素。所以如果将列表中哈希键-值对之间的逗号换为胖箭头,就能更加清晰直观地体现出键-值对的对应关系。胖箭头的另一个作用为可以隐式引起胖箭头左边的字符串,从而省略左边字符串的引号。而如果左侧字符串为表达式,则其会被先计算,再被隐式引起。
在使用胖箭头时,应注意左边未加引号的部分是否出现了会引起歧义的符号,如加号,乘号等,如果出现这种情况,则左边的字符串必须显示引起。例:
%hash=(
a=>'aaa',
b=>'bbb',
c=>'ccc',
); #用含有胖箭头的列表给哈希赋值
在列表上下文中,哈希会展开为一个含有键-值对的列表。这一操作称为展开哈希。例:
@n=%hash; #展开哈希,并将其赋值给数组
哈希也可以赋值给哈希,右边的哈希会先展开成键-值对列表,然后将此列表赋值给哈希。常见的操作如复制一个新哈希或建立反序哈希。例:
%new_hash=%old_hash; #复制哈希
%hash=reverse%hash; #建立反序哈希
所谓反序哈希,即将哈希展开为键-值对列表后,对列表进行反序,然后再赋值给哈希。这样的操作会颠倒每一对键-值对的顺序,使原来的键变为值,而使原来的值变为键。显然,这种操作不适用于哈希值有重复的情况,因为这样会导致颠倒后哈希键的重复。当重复的键又一次被赋值时,后值会覆盖原先的值,并且由于哈希的无序性,也无从得知是哪一个值在前哪一个在后。
在标量上下文中,非空哈希会返回一个类似分数的字符串,供内部调试使用,而空哈希会返回0,所以普通用户可以将其当作布尔值使用,用以判断哈希是否为空。
访问哈希元素与哈希元素内插
要访问单个哈希元素,就可以使用$开头表示调用单个值,后接哈希名称,再后接用一对花括号围住的哈希键。由于哈希键是字符串,所以原则上应使用引号引起,但在实际操作中,如果没有歧义产生,绝大多数情况下花括号内的哈希键引号均可省略。如果花括号内为表达式,则该表达式会被计算。例:
$hash{b} #调用键为b的哈希值
for(qw/a b c/){print$hash{$_}} #使用$_循环迭代键列表,并输出键为$_的哈希值,“$_”这个表达式在调用哈希时会先被计算
类似于列表中对单个元素的赋值,哈希中对单个哈希键赋值也可以通过调用单个哈希元素这种方式。例:
$hash{b}=bbb; #对哈希键b进行赋值,省略了字符串引号
哈希元素也可以进行双引号内插。但只能内插单个哈希值,而不能内插整个哈希。内插整个哈希时,不会产生任何效果,百分号会被当作字符串处理。例:
for(qw/a bc/){print"$_=>$hash{$_}\n"} #双引号内插哈希值
print"%hash"; #无任何效果,结果就是"%hash"
keys和values函数
Perl中有许多的函数可以用于处理哈希。keys函数是最常用的哈希函数之一,其参数为一整个哈希,可以返回此哈希的键列表。例:
@n=keys%hash; #返回%hash的键列表,并存入@n中
for(keys%hash){print$hash{$_}} #首先由keys返回%hash的键列表,然后遍历所有键,并输出哈希值
values函数与keys类似,其可以返回哈希值列表。例:
@m=values%hash; #返回%hash的值列表,并存入数组m中
当一个哈希中新增键-值对时,哈希元素的先后顺序会被重排,而其他情况,如修改哈希值、删除键-值对或调用哈希函数等均不会使哈希元素重排,这就是哈希重排的规则。所以当同时使用keys和values函数返回两个列表时,会发现这两个列表中键-值对的顺序是一一对应的。
在标量上下文中,这两个函数都会返回列表元素个数,即哈希中键-值对的数量。例:
$_=keys%hash; #$_的值即哈希中键-值对的数量
哈希排序初步
由于哈希的无序性,哈希本身是无法排序的。而所谓的哈希排序,本质上就是利用哈希键的唯一性,按一定的规则对哈希键列表进行排序,从而得到一个排序后的键列表,然后再通过键与值之间的对应关系来处理哈希值。
所以在哈希排序时,可以先使用keys函数取出哈希键列表,再使用排序函数sort对这个列表进行排序,从而得到一个排序后的哈希键列表。例:
for(sortkeys%hash){print"$_=>$hash{$_}\n"}
上例中,首先用keys函数取出哈希键列表,此列表作为sort函数的参数进行排序,这样就得到了排序后的哈希键列表。这个列表再作为foreach循环的参数进行循环迭代,最终输出的便是以排序后的键列表的顺序调用的哈希键-值对。
each函数
类似于foreach循环,如果需要遍历哈希中的每一对键-值对,除了用keys取出键列表进行循环外,还可以使用each函数。each函数实际上是一个作用于列表的函数,即作用于哈希展开得到的列表。每次对列表调用此函数时,each都会返回列表中的下两个元素形成的列表。而对哈希使用时,这两个元素就是一对哈希键-值对。在实际使用中,唯一适合使用each函数的地方就是在while循环的条件表达式中。每循环一次,each函数会从哈希中取出下一个键-值对,从而执行代码块。例:
while(($n,$m)=each%hash){print"$n=>$m\n"}
上例的机制比较复杂。首先,each函数从哈希中取出一对键-值对,然后赋值给列表。由于while的条件表达式是布尔上下文,即一种特殊的,返回布尔值的标量上下文,所以列表会返回列表中元素的个数2,判定为布尔真值,所以执行循环。当each迭代到哈希的末尾时,则会返回一个空列表并赋值,而空列表由于没有元素,所以在布尔上下文中返回0,即布尔假值,从而跳出循环。
每一个哈希都含有一个自己独立的迭代器,用于记忆上一次循环所访问的位置,从而使each能每次取出下一个键-值对。在某些情况下,迭代器会被重置。
exists和delete函数
exists函数可以检查哈希中是否存在指定的键,如存在,则返回真值,常用于条件表达式中。这个函数仅检查哈希键的存在与否,与哈希值的真假无关,可以理解为该键是否能出现在keys返回的列表中。例:
if(exists$hash{b}){print'T'}else{print'F'} #检查是否存在$hash{b}这个键
delete函数可以从哈希中删除指定的键-值对。如果哈希中不存在这样的键值对,则会直接略过。例:
delete$hash{b}; #删除键为b的这个键-值对
樱雨楼
完成于2016.1.3
最后修改于2016.1.26
网友评论