哈希表

190 阅读2分钟

map原理

  1. map是由key-value对组成的,key只会出现一次

  2. 它的任务是设计一种数据结构用来维护一个集合的数据,并且可以同时对集合进行增删查改的操作。最主要的数据结构有两种:哈希查找表(Hash table)搜索树(Search tree)

  3. 哈希函数---散列函数:是可以用于将任意大小的数据映射到固定大小值的函数,常见的包括MD5、SHA系列等。

image.png

  1. 哈希冲突:当输入数据足够大,大到能超过固定大小值的组合能表达的最大数量数,冲突将不可避免!

juejin.cn/post/711681…

image.png

image.png

  1. 哈希查找表用一个哈希函数将 key 分配到不同的桶(bucket,也就是数组的不同 index)。
  2. 哈希查找表一般会存在“碰撞”的问题,就是说不同的 key 被哈希到了同一个 bucket。解决哈希冲突一般有两种应对方法:链表法开放地址法链表法将一个 bucket 实现成一个链表,落在同一个 bucket 中的 key 都会插入这个链表。开放地址法则是碰撞发生后,通过一定的规律,在数组的后面挑选“空位”,用来放置新的 key

image.png

  1. 搜索树法一般采用自平衡搜索树,包括:AVL 树,红黑树。

  2. Go 语言采用的是哈希查找表,并且使用链表解决哈希冲突。

源码:

type hmap struct {
    count     int // 代表哈希表中的元素个数,调用len(map)时,返回的就是该字段值。
    flags     uint8 // 状态标志,下文常量中会解释四种状态位含义。
    B         uint8  // buckets(桶)的对数log_2(哈希表元素数量最大可达到装载因子*2^B)
    noverflow uint16 // 溢出桶的大概数量。
    hash0     uint32 // 哈希种子。

    buckets    unsafe.Pointer // 指向buckets数组的指针,数组大小为2^B,如果元素个数为0,它为nil。
    oldbuckets unsafe.Pointer // 如果发生扩容,oldbuckets是指向老的buckets数组的指针,老的buckets数组大小是新的buckets的1/2。非扩容状态下,它为nil。
    nevacuate  uintptr        // 表示扩容进度,小于此地址的buckets代表已搬迁完成。
    extra *mapextra // 这个字段是为了优化GC扫描而设计的。当key和value均不包含指针,并且都可以inline时使用。extra是指向mapextra类型的指针。

image.png

0.png

image.png

  1. 每个桶只能存八个key-value,如果超了就新建一个桶,通过 overflow 指针连接起来。

创建map

  1. 调用makemap函数,初始化hmap结构体,返回*hmap指针

image.png

image.png

image.png

哈希函数

key定位

key 经过哈希计算后得到哈希值,共 64 个 bit 位。计算它到底要落在哪个桶时,只会用到最后 B 个 bit 位。如果 B = 5,那么桶的数量,也就是 buckets 数组的长度是 2^5 = 32。

image.png

image.png