Perl-哈希

1. 什么是哈希

哈希是1种数据结构,像数组一样可以容纳任意多的值,并可按需取用,与数组不同的是,哈希与数组的检索方式不同,数组以数字下标来检索,哈希以唯一的名字来检索,这个唯一的名字叫做键

哈希的字符串只能用普通的字符串表示,可以用任意字符串表达式作为哈希键,这些键必须是唯一的字符串

可以把哈希想象称一大桶数据,但是桶里没有第一个数据,只有一堆数据,哈希内的数据没有顺序,因此没有所谓的第1个元素,有的只是一堆键-值对的组合

哈希的键和值都是任意的标量,但哈希的键总会被转换成字符串,如果以数字表达式50 / 20为键,那么这个键实际上为字符串“2.5”

根据perl“去除不必要限制”的原则,哈希的数据结构容量可以是任意大小,从没有任何键-值对的空哈希到填满内存的巨大哈希都可以

某些语言也有哈希表(比如awk语言),但这些语言在键-值对增多时检索速度会逐渐变慢。如果某个哈希包含300万个键-值对,从中提取任意一项数据还是会和原来一样快。所以我们不用担心大数据量的哈希读写性能。

虽然哈希的键是唯一的,但是哈希对应的值是可以重复的
哈希的值可以是数字、字符串、undef,或是这些类型的组合
但哈希的键必须是字符串且不可重复

2. 为何使用哈希?

需要将1组数据对应到另1组数据
可以把哈希当成一种极简数据库,每个键对应存储一块数据
如果我们要处理的问题的描述中带有“找出重复项”、“唯一的”、“交叉引用”、“查表” 之类的字眼的话,很可能需要用到哈希

3. 访问哈希元素

和访问数组的做法类似,只是用了花括号而非方括号来表示索引键,并且键表达式是字符串,而非数字

$hash{$some_key}$family_name{'fred'} = 'flintstone';
$family_name{'barney'} = 'rubble';

foreach my $person (qw< barney fred >){
	print "I've heard of $person $family_name{$person}\n";
}

哈希变量的命名和其它perl标识符相似,可以由字母、数字、下划线构成,但不能用数字开头
此外,哈希与标量、数组及子程序一样,有自己的命名空间,也就是说$family_name{‘fred’}和子程序 &family_name之间毫无关联

定义哈希键是可以使用任意表达式,字符串、简单标量变量等,求值结果按字符串当成键

$foo = 'bar';
print $family_name{$foo.'ney'} ; # 打印 'rubble' 

若对某个已存在的哈希元素赋值,就会覆盖之前的值

$family_name{'fred'} = 'astaire'; # 给已有的元素赋上新值
$bedrock = $family_name{'fred'}; # 得到"astarie",之前的值已抛弃

哈希元素会因赋值而诞生

$family_name {'wilma'} = 'flintstone'; # 增加1个新的键-值对
$family_name {'betty'} .= $family_name{'barney'}; # 在需要的时候动态创建该元素

这种特性称为自动延展

访问哈希中不存在的值会得到undef

$granite = $family_name{'larry'}; # 没有larry这个键,所以值为undef

4. 访问整个哈希

百分号作为前缀,来指代整个哈希,比如%family_name
为了定义和解构方便,哈希可以转换成列表,或者通过列表构造哈希
在列表上下文中对哈希赋值,列表中的每个元素将依次作为键-值对,构造一个新的哈希

%some_hash = ( 'foo', 35, 'bar', 12.4, 2.5, 'hello', 'wilma', 1.72e30, 'betty', "bye\n");

虽然可以使用任意长度的列表,但由于构成哈希的键-值对是偶数个,所以对应列表也应该有偶数个元素,否则最后1个键对应的值会是undef

在列表上下文中,哈希返回的是键-值对组成的列表

@any_array = %some_hash;

这个变换叫做哈希松绑(unwinding),键-值对重新变回列表,此时的键-值对和当初赋值时的顺序基本上不会相同

print "@any_array\n";

之所以顺序乱掉,是因为perl已经为哈希的快速检索对键-值对的存储作了特别排序

即使键-值对的顺序被打乱,列表里的每个键还是会黏着相应的值,所以无法知道某个键foo出现在列表的哪个位置,仍然可以确信对应值35会跟在后面

5. 哈希赋值

my %new_hash = %old_hash; # 语法上可行,但是很少这么做

上面这行代码会先把%old_hash松绑为键-值对列表,然后通过列表赋值重新构造每个键-值对,最终形成新的哈希%new_hash

可以对哈希做一些变形,比如反转键值对,得到逆向检索的新哈希

my %inverse_hash = reverse %any_hash;

这会把%any_hash解绑为键值对列表,然后利用reverse的列表翻转功能,获取新列表,达成键值互换,但是这种技巧只能在哈希值不重复的情况下才能生效,因为哈希的键必须唯一

键值对在哈希松绑后的顺序是无法预知的,所以无法预知最后哪些键值对会被覆盖

6. 胖箭头

使用列表对哈希赋值时很难区分键和值的成对关系
胖箭头(=>)只是逗号的另一种写法,在任何需要逗号的地方都可以用胖箭头代替

%some_hash = ( 'foo', 35, 'bar', 12.4, 2.5, 'hello', 'wilma', 1.72e30, 'betty', "bye\n" );

my %some_hash = (
	'fred' => 'flintstone',
	'dino' => undef,
	'barney' => 'rubble',
	'betty' => 'rubble',
);

列表末尾有个逗号,是为了便于维护,perl会直接忽略多余的逗号
由于表示键的字符串一般都用简单的词,所以胖箭头左侧的键可以直接省略引号,当不加引号会产生歧义时,则必须加上引号

my %some_hash = (
	fred => 'flintstone',
	dino => undef,
	barney => 'rubble',
	betty => 'rubble',
);

my %last_name = (
	+ => 'flintstone';
);

如果键名只是由字母、数字和下划线组成,并且不以数字开头,就可以省略引号,这类无需引号的字符序列,我们称之为裸字(bareword),因为它是孤立存在的

另外一个常见允许省略键名引号的地方是:定义在花括号中的键名,$score{'fred'} 可以简写为$score{fred},不过如果花括号内的不是裸字,perl就会将其当作表达式先求值,然后把结果当作键名

$hash{bar.foo} = 1; # 构成键名'barfoo'

7. 哈希操作函数

keys和values函数
keys函数返回的是哈希的键列表
values函数返回的是哈希的值列表
如果哈希中没东西,则返回空列表

my %hash = ( 'a' => 1, 'b' => 2, 'c' => 3);
my @k = keys %hash;
my @v = values %hash;

返回的顺序可能会有所不同,因为哈希按自己检索的需要维护存储顺序,但返回的键列表及对应的值列表间的对应顺序是一致的

但如果新增某个元素的话,perl可能根据需要重新优化排列顺序,以保证后续高速检索的能力
在标量上下文中,keys和values函数都会返回哈希中键值对的个数,由于这个计算过程不需要对整个哈希进行遍历,因为非常高效

my $count = keys %hash; # 返回3,说明有3对键值

可以把哈希当成布尔表达式来判断真假

if (%hash){
	print "That was a true value!\n";
}

只要哈希中至少有1个键值对,就返回真

8. each函数

可以用each函数来遍历哈希的每个键值对,每次调用each函数,它都会以列表形式返回2个元素,1个是键名,1个是值,再次调用它会返回下一组键值对,直到所有元素都遍历完毕,没有数据返回时,each函数会返回空列表
each在while循环中很有用

while (($key, $value) = each %hash){
	print "$key => $value\n";
}

哈希会记着上次访问的位置,每个哈希都有1个迭代器(iterator)

由于每个哈希都有自己的迭代器,因此处理不同哈希的each调用可以嵌套。不过如果嵌套时each调用的是同一个哈希,就会相互干扰,最终出现混乱的结果

each返回键值对的顺序是乱的,但each函数返回的顺序和keys与values返回的顺序相同,也就是哈希的自然顺序,如果需要依次处理哈希,只要对键排序就行了

foreach $key (sort keys %hash){
	$value = $hash{$key};
	print "$key => $value";
}

9. 哈希的典型应用

哈希里有些元素的值并不为真

$books{"barney"} = 0; # barney现在没有借书
$books{"pebbles"} = undef; # pebbles新办了一张借书证

10. exists函数

检查哈希中是否存在某个键,可以用exists函数,键存在的话它会返回真,不存在的话它会返回假

if (exists $books{"dino"}){
	print "Hey, there's a library card for dino!\n";
}

11. delete函数

delete能从哈希中删除指定的键及其对应的值,如果没有这样的键,就直接结束,不会出现任何警告或错误信息

my $person = 'betty';
delete $books{$person}; # 销毁$person的借书证

这并不是将undef存入哈希元素,在delete之后,哈希中就没有这个键了,但存入undef之后,键是一定会存在的

12. 哈希元素内插

可以将单一哈希元素内插到双引号引起的字符串中

foreach $person (sort keys %books){ # 按次序访问每位借阅者
	if($books{$person}){ 
	print "$person has $book{$person}"; # fred借了3本书
	}
}

但这种方式不支持内插整个哈希,"%books"的结果只是"%books"!!!
在双引号中需要转义的魔力字符有 $ 和 @ , 双引号, \ 本身也需要转义,除此外,双引号中任何字符都只代表它们自己
在双引号中变量名后的撇号(’)、左方括号([)、左花括号({)、瘦箭头(->)和双冒号(::),它们是表示变量内容的一部分,而非字符本身

13. 特殊哈希 %ENV

从%ENV中读取键为PATH的值

print "PATH is $ENV{PATH}";

可以自己添加需要的环境变量

$ export CHARACTER = Fred # bash

C:\> set CHARACTER = Fred # windows

预先在外部设定好环境变量后,perl程序就可以读到它当前的值

print "CHARACTER is $ENV{CHARACTER}\n";
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值