golang实现hashmap,自动伸缩

golang实现hashmap数据结构

简介

哈希表是存放键值对的数据结构,在哈希表中进行删除、插入操作几乎能达到O(1)的时间效率。简单来说,哈希表是数组与链表的结合,为了更好理解哈希表,先看我测试hashmap时的一段输出,比较形象的描绘了哈希表的结构

0:—–>&{20 atongmu }
1:—–>&{61 atongmu }
2:
3:
4:
5:
6:
7:—–>&{67 atongmu }
8:—–>&{68 atongmu }
9:
10:
11:
12:
13:
14:
15:—–>&{35 atongmu }
16:—–>&{16 atongmu 0xc0420024e0}—–>&{76 atongmu }
17:—–>&{57 atongmu }
18:—–>&{58 atongmu }
19:—–>&{79 atongmu }

这一整块便是一个哈希表了,它由表头和数据链组成。最左侧的一列是表头,这一列也就是由数组构成的了,每个表头不存放数据,它只有数据链的指针,如果没有entry存放在该位置,其指针就为nil。数组的下标表示表头在bucket中额位置,也是每个key的hash值。右边的所有东西就是数据链了,为了让哈希表在空间占用轻量的同时,尽量达到O(1)的查找、插入、删除的时间效率,需要通过负载因子自动调节bucket的长度。

主要特点

  1. 我的hashmap使用的key接收int,value接收string
  2. 当负载因子(已有entry数/bucket容量)大于0.75时自动扩容
  3. put时如果发现hashmap中已有该key,直接覆盖其value
  4. put时如果发现key的hash值在表中已有结点,则将其附加在该结点所在链表尾部

关键类型、常量、变量、及函数介绍

1. Entry和HashMap类型

type Entry struct {
    key   int
    value string
    next  *Entry
}  

Entry用来保存键值对及下一个Entry的指针,它描述hashmap中的每一个结点

type HashMap struct {
    size    int
    buckets []Entry
}

HashMap类型就是整个hashmap了,它描述其大小,用Entry类型的切片实现

2. 全局常量、变量

const maxCapacity int = 100    
const loadFactor float32 = 0.75     
var nowCapacity int = 10   

maxCapacity定义了hashmap中bucket的最大扩充容量,即,虽然hashmap可以自动扩充大小一提高使用效率,但不能无休止地扩充。
loadFactor定义负载因子,当表中entry数量达到当前容量的负载因子时(此处定义为0.75),容量翻倍
nowCapacity作为变量保存当前表的负载能力,也就是bucket的长度

3.CreateHashMap()新建hashmap

func CreateHashMap() *HashMap {
    hm := &HashMap{}
    hm.size = 0
    hm.buckets = make([]Entry, nowCapacity, maxCapacity)
    return hm
}

用户只能通过调用此函数建立并初始化hashmap,它初始化了hashmap的size(初始Entry个数)、初始负载以及最大负载

4. getHash()获取key的散列值

func getHash(k int) int {
    return k % nowCapacity
}

因为我定义的key为int类型,所以就通过简单的除留余数法获取key的散列值

5. DeleteEntry(k int)在hashmap中删除某个key对应的Entry

//删除指定key的Entry
func (hm *HashMap) Delete(k int) bool {
    e, ok := hm.GetEntry(k)
    if ok {
        fmt.Println("有该Entry,其hash为:", getHash(e.key), "值为:", e)
        hm.DeleteEntry(e)
        return true
    } else {
        fmt.Println("没有该Entry,删除失败")
        return false
    }
}

调用了封装好的GetEntry(key int)和DeleteEntry(e *Entry),先找到该key所在的Entry,在执行删除

6. Put(k int,v value)向hashmap中插入数据,并检测负载程度自动调节容量

func (hm *HashMap) Put(k int, v string) {

    e := &Entry{k, v, nil}
    hm.insert(e)
    //达到负载因子且还能扩容时,扩容并迁移数据
    if float32(hm.size)/float32(nowCapacity) >= loadFactor && nowCapacity < maxCapacity {
        if 2*nowCapacity > maxCapacity {
            nowCapacity = maxCapacity
        } else {
            nowCapacity = 2 * nowCapacity
        }
        newHm := CreateHashMap()

        var index int
        for index = 0; index < len(hm.buckets); index++ {
            p := hm.buckets[index].next
            for p != nil {
                //临时保存p的next
                pNext := p.next
                p.next = nil
                newHm.insert(p)
                p = pNext
            }
        }

        var b1 int
        var b2 int = len(newHm.buckets)

        //把hm的切片扩容至newHm一样大
        for b1 = len(hm.buckets); b1 < b2; b1++ {
            hm.buckets = append(hm.buckets, Entry{})
        }
        //移动回数据
        for b1 = 0; b1 < b2; b1++ {
            hm.buckets[b1].next = newHm.buckets[b1].next
            newHm.buckets[b1].next = nil
        }
    }
}

先调用insert函数直接插入键值对到hashmap中,再检测是否需要扩容,float32(hm.size)/float32(nowCapacity) >= loadFactor表示已超出负载因子,nowCapacity < maxCapacity表示还有扩容空间,满足这两个条件时,便新建容量翻倍(或直接值maxCapacity)的新的hashmap,再迁移其数据

完整代码实现

package hashmap

import (
    "fmt"
)

const maxCapacity int = 100     //bucket的最大容量
const loadFactor float32 = 0.75 //负载因子

var nowCapacity int = 10 //当前容量

type Entry struct {
    key   int
    value string
    next  *Entry
}

type HashMap struct {
    size    int
    buckets []Entry
}

func CreateHashMap() *HashMap {
    hm := &HashMap{}
    hm.size = 0
    hm.buckets = make([]Entry, nowCapacity, maxCapacity)
    return hm
}

//获取key的hash
func getHash(k int) int {
    return k % nowCapacity
}

func (hm *HashMap) GetSize() int {
    return hm.size
}

//由key在hashMap中找到其Entry指针
func (hm *HashMap) GetEntry(k int) (*Entry, bool) {
    p := hm.buckets[getHash(k)].next
    for p != nil {
        //找到,返回
        if p.key == k {
            return p, true
        } else {
            p = p.next
        }
    }
    //此时p==nil,没找到
    return nil, false
}

//不判断是否达到负载因子,直接往hashmap中插entry
func (hm *HashMap) insert(e *Entry) {

    hash := getHash(e.key)
    //从表头中的指针开始遍历
    var p *Entry = &hm.buckets[hash]
    for p.next != nil {
        //如果找到相同的key,则覆盖其中的值,完成insert,return
        if p.next.key == e.key {
            p.next.value = e.value
            return
        } else {
            p = p.next
        }
    }
    //此时p指向尾部结点
    p.next = e
    hm.size++
}

//判断是否达到负载因子再insert
func (hm *HashMap) Put(k int, v string) {
    e := &Entry{k, v, nil}
    hm.insert(e)
    //达到负载因子且还能扩容时,扩容并迁移数据
    if float32(hm.size)/float32(nowCapacity) >= loadFactor && nowCapacity < maxCapacity {
        if 2*nowCapacity > maxCapacity {
            nowCapacity = maxCapacity
        } else {
            nowCapacity = 2 * nowCapacity
        }

        newHm := CreateHashMap()
        var index int
        for index = 0; index < len(hm.buckets); index++ {
            p := hm.buckets[index].next
            for p != nil {
                //临时保存p的next
                pNext := p.next
                p.next = nil
                newHm.insert(p)
                p = pNext
            }
        }

        var b1 int
        var b2 int = len(newHm.buckets)

        //把hm的切片扩容至newHm一样大
        for b1 = len(hm.buckets); b1 < b2; b1++ {
            hm.buckets = append(hm.buckets, Entry{})
        }
        //移回数据
        for b1 = 0; b1 < b2; b1++ {
            hm.buckets[b1].next = newHm.buckets[b1].next
            newHm.buckets[b1].next = nil
        }
    }
}

//删除指定Entry
func (hm *HashMap) DeleteEntry(e *Entry) {
    //注意这里不能是p:=hm.buckets[getHash(e.key)].next,因为表头存的指针不一定为nil
    p := &hm.buckets[getHash(e.key)]
    for p != nil {
        if p.next == e {
            fmt.Println("正在删除")
            p.next = p.next.next
            fmt.Println("删除成功")

            hm.size--
            return
        } else {
            p = p.next
        }
    }
    fmt.Println("删除失败")
}

//删除指定key的Entry
func (hm *HashMap) Delete(k int) bool {
    e, ok := hm.GetEntry(k)
    if ok {
        fmt.Println("有该Entry,其hash为:", getHash(e.key), "值为:", e)
        hm.DeleteEntry(e)
        return true
    } else {
        fmt.Println("没有该Entry,删除失败")
        return false
    }
}

//遍历hashMap
func (hm *HashMap) Traverse() {
    var index int
    for index = 0; index < nowCapacity; index++ {
        p := hm.buckets[index].next
        if p == nil {
            fmt.Println(index, ":")
        } else {
            fmt.Print(index, ":")
        }
        for p != nil {
            fmt.Print("----->", p)
            p = p.next
            if p == nil {
                fmt.Println()
            }
        }
    }
    fmt.Println()
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值