Golang Map 详解-第一部分:什么是Map

正在学习Golang Map,看了几篇博客,特此记录下来,刚开始写,写的不好的地方欢迎指出。

什么是Map

在维基百科中是这样定义Map的

In computer science, an associative array, map, symbol table, or dictionary is an abstract data type composed of a collection of (key, value) pairs, such that each possible key appears at most once in the collection.

简单点说Map主要有两个关键点:map是由key-value键值对组成的;key只会出现一次。

map就像是一本字典,它就是一种数据结构用来key value 集合,并且可以根据key来进行增删改查。要想实现一个优秀的哈希表,主要是注意两个点:哈希函数和冲突解决办法。

哈希函数

哈希函数就是应该将不同的key映射到不同的索引上,映射的冲突越少效果越好,但同时也要考虑哈希函数的计算时间。使用了好的哈希函数,那么map的增删改查的时间复杂度为O(1)。

在程序启动时,会检测CPU是否支持aes,如果支持,则使用aes hash,否则使用memhash,这是在位于src/runtime/alg.go的函数alginit()中完成的。

hash 函数,有加密型和非加密型。
加密型的一般用于加密数据、数字摘要等,典型代表就是 md5、sha1、sha256、aes256 这种;
非加密型的一般就是查找。在 map 的应用场景中,用的是查找。
选择 hash 函数主要考察的是两点:性能、碰撞概率。

冲突解决

我们之前提到过,哈希函数计算过后的索引是会产生冲突的,那么如何解决冲突的索引呢,常见的解决办法也就是:开放寻址法和拉链法。

开放寻址法

开放地址法有一个非常关键的特征,就是所有的输入元素全部存放在数组中。也就是这个哈希表的装载因子不会超过1,因为数组的长度总会大于哈希的元素个数。它的实现是在插入一个元素的时候,先通过哈希函数进行判断,若是发生哈希冲突,就以当前地址为基准,根据再寻址的方法(探查序列),去寻找下一个地址,若发生冲突再去寻找,直至找到一个为空的地址为止。所以这种方法又称为再散列法。

几种常用的探查序列的方法:

  1. 线性查找:dii=1,2,3,…,m-1;这种方法的特点是:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。
  2. 二次探查:di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 );这种方法的特点是:冲突发生时,在表的左右进行跳跃式探测,比较灵活。
  3. 伪随机探测:di=伪随机数序列;具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),生成一个位随机序列,并给定一个随机数做起点,每次去加上这个伪随机数++就可以了。

例如当向哈希表中写入(author,hank)这个键值对时会从如下的索引开始遍历:index := hash("author") % array.len。当向哈希表中写入新的数据时,如果发生了冲突,就会将键值写入到下一个索引不为空的位置:

2019-12-30-15777168478785-open-addressing-and-set

开放寻址法在非常小规模的数据存储中非常的节省空间。但是它也有很多的缺点:

  • 哈希函数的设计对冲突会有很大的影响。
  • 容易产生堆积问题。
  • 插入时可能会出现多次冲突的现象,删除的元素是多个冲突元素中的一个,需要对后面的元素作处理,实现较复杂。
  • 结点规模很大时会浪费很多空间。
拉链法

绝大部分的编程语言都是采用的拉链法实现哈希表。它的基本思想是使用链表来解决哈希冲突,某些哈希地址可以被多个关键字值共享,针对每个哈希地址建立一个链表,也就是数组加链表的方式。例如当向哈希表中写入(author,hank)这个键值对时还是会从如下的索引开始遍历:index := hash("author") % array.len。但是要注意array.len已经减小,其实这步操作是选择一个桶。当向哈希表中写入新的数据时,如果发生了冲突,就会在当前位桶的链表末尾追加新的键值对。

2019-12-30-15777168478798-separate-chaing-and-set

使用拉链法实现的Map中读写操作的主要开销就是计算哈希、定位桶和遍历链表。它的装载因子是:

装载因子 := 元素个数 / 桶数量

拉链法实现上会复杂一点,但是它的优点也是有很多的:

  • 处理冲突简单,且无堆积现象,平均查找长度短。
  • 链表中的结点是动态申请的,适合构造表不能确定长度的情况。
  • 开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间
  • 在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值