golang 的 map 实现(一)

概述

哈希表是工程中常用到的数据类型,能提供快速的检索和更新。复杂度一般为 O(1)

本篇博文分 两部分写,第一部分是源码学习,第二部分是一些内部实现,以及觉着有意思的一些地方,以及个人思考

理论

哈希表需要解决的问题有两个

  • 位置索引
  • 数据碰撞

索引交给 hash function 哈希算法,常用就是模运算

解决碰撞主要有以下三种方式

  1. 分离链接,也就是利用链表性质存储冲突的 key,然后通过遍历来区分(单独的存储层面)
  2. 开放定址
    1. 线性探测(存储层面以及算法层面都有所调整)
    2. 平方探测(同上,只是算法层面小改动而已)
    3. 双散列
  3. 再散列 (扩容以及数据迁移)

可扩散列是用来解决数据太大而无法装进内存的场景,此处不讨论

哈希表的效率load factor 装填因子有关,用来估量其平均复杂度。含义就是一个其计算方式一般就是使用 已经存储的数据量 / 可索引地址的数量。 或者说,单个索引地址的平均长度

碰撞的解决

理想情况下,没有碰撞的时候,使用一个数组,与一个哈希算法就可以实现散列结构。但是碰撞无法完全避免,那么就有了以下几种方式来解决。

分离链接

分离链接的核心是通过使用链表来处理碰撞问题。数组用来做索引,内部存储链表,链表存储的是哈希碰撞的 key 以及 value,存储 key 是为了在冲突的时候,仍旧可以通过比对来实现定位。

开放定址

链表的问题是节点申请,会造成内存的频繁操作。如果在数据量不是特别大的时候,可以考虑开放定址的方式。其仍旧使用一个比较大的数组。只是在发生碰撞的时候,可以通过向固定方向进行偏移来进行存储,从而解决碰撞问题。
线性探测和平方探测就在于偏移量选择上。双散列(略)

再散列

碰撞某种程度上可以说是存储空间较小造成的。那么 rehash 的思想就是,申请更大的空间,然后将数据重新计算,重新定位。

Golang 的 map 实现

golang 中的 map 是一个哈希表,其实现方式使用到了链表以及 rehash。

链表是在用在较小层面碰撞,rehash 则是当 load factor 较大的时候使用的方式。

注意:本篇记录是基于 go 1.9.2 版本记录的。

数据结构

golang 的 map 内并没有直接存储传递进来的 keyvalue,而是使用了其引用,以及 key 的 hash 值的高位(后面再说)。

下面是 map 数据结构的部分,选取了主要是跟存储相关的域。

type hmap struct {
    B          uint8
    buckets    unsafe.Pointer
    oldbuckets unsafe.Pointer
    extra      *mapextra
}

buckets 与 oldbuckets 是指向一段连续地址的指针地址。主要是用来存储 key 和 value 的引用地址,暂时理解成数据部分好了。其中oldbuckets 只有在扩容的时候才会用到。两者与前面『分离链接』实现中的数组功能类似,供初步索引使用。

type mapextra struct {
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值