go1.18beta1 泛型demo: hashmap

go终于出泛型了,整个烂活玩玩,体验一下,总体感觉还是有点别扭,主要有两处不完善

// hashmap.go
package hashmap

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"hash/fnv"
	"reflect"
)

type HashMap[K comparable, V any] struct {
	table []*entry
	sz    int
}

type entry struct {
	key   any
	value any
	next  *entry
}

func NewHashMap[K comparable, V any](cap int) *HashMap[K, V] {
	cap = powOfTwo(cap)
	table := make([]*entry, cap, cap)
	return &HashMap[K, V]{table: table}
}

const MaxCap = 1 << 30

func powOfTwo(cap int) int {
	n := cap - 1
	n |= n >> 1
	n |= n >> 2
	n |= n >> 4
	n |= n >> 8
	n |= n >> 16
	if n < 0 {
		return 1
	}
	if n >= MaxCap {
		return MaxCap
	}
	return n + 1
}

func (m *HashMap[K, V]) Put(key K, value V) {
	factor := float32(m.sz) / float32(len(m.table))
	if factor > 0.75 {
		m.resize()
	}
	h := m.hash(key)
	idx := h & (uint64(len(m.table) - 1))
	e := m.table[idx]
	for e != nil {
		if reflect.DeepEqual(e.key, key) {
			e.value = value
			return
		}
		e = e.next
	}
	n := &entry{key: key, value: value}
	n.next = m.table[idx]
	m.table[idx] = n
	m.sz++
}

func (m *HashMap[K, V]) Get(key K) (v V, ok bool) {
	h := m.hash(key)
	idx := h & (uint64(len(m.table) - 1))
	e := m.table[idx]
	for e != nil {
		if reflect.DeepEqual(e.key, key) {
			return e.value.(V), true
		}
		e = e.next
	}
	return
}

func (m *HashMap[K, V]) Remove(key K) (v V, ok bool) {
	defer func() {
		if ok {
			m.sz--
		}
	}()
	h := m.hash(key)
	idx := h & (uint64(len(m.table) - 1))
	e := m.table[idx]
	if reflect.DeepEqual(e.key, key) {
		m.table[idx] = m.table[idx].next
		return e.value.(V), true
	}
	for e.next != nil {
		if reflect.DeepEqual(e.next.key, key) {
			v = e.next.value.(V)
			e.next = e.next.next
			return v, true
		}
		e = e.next
	}
	return
}

func (m *HashMap[K, V]) Size() int {
	return m.sz
}

func (m *HashMap[K, V]) Foreach(f func(key K, value V)) {
	for _, e := range m.table {
		if e == nil {
			continue
		}
		for e != nil {
			f(e.key.(K), e.value.(V))
			e = e.next
		}
	}
}

func (m *HashMap[K, V]) resize() {
	newC := uint64(len(m.table)) << 1
	newTable := make([]*entry, newC, newC)
	m.Foreach(func(key K, value V) {
		h := m.hash(key)
		idx := h & (newC - 1)
		n := &entry{key: key, value: value}
		n.next = newTable[idx]
		newTable[idx] = n
	})
	m.table = newTable
}

func (m *HashMap[K, V]) hash(obj K) uint64 {
	b := bytes.Buffer{}
	_ = gob.NewEncoder(&b).Encode(obj)
	h := fnv.New64a()
	_, _ = h.Write(b.Bytes())
	return h.Sum64()
}

func (m *HashMap[K, V]) String() string {
	buf := bytes.NewBufferString("[")
	i := 0
	m.Foreach(func(key K, value V) {
		buf.WriteString(fmt.Sprintf("<%v,%v>", key, value))
		if i != m.sz-1 {
			buf.WriteByte(',')
		}
		i++
	})
	buf.WriteRune(']')
	return buf.String()
}

  测试代码:

// main.go
package main

import (
	"./hashmap"
	"fmt"
)

func main() {
	m := hashmap.NewHashMap[string, int](3)
	m.Put("stan", 1)
	m.Put("kyle", 2)
	m.Put("kenny", 3)
	m.Put("eric", 4)
	m.Put("butters", 5)
	m.Put("craig", 6)
	m.Put("clyde", 7)
	m.Put("jimmy", 8)
	m.Put("token", 9)
	m.Remove("eric")
	m.Remove("butters")
	fmt.Println(m.Get("stan"))
	fmt.Println(m.Get("kyle"))
	fmt.Println(m.Get("kenny"))
	fmt.Println(m.Get("eric"))
	fmt.Println(m.Get("butters"))
	fmt.Println(m.Get("craig"))
	fmt.Println(m.Get("clyde"))
	fmt.Println(m.Get("jimmy"))
	fmt.Println(m.Get("token"))
	fmt.Println("-----------------------------")
	fmt.Println(m)
}

  基本使用确实没什么问题,但是有两处地方有点别扭

1. hashmap.go第56行、73行等处必须使用reflect.DeepEqual(e.key, key)而不是直接e.key == key。

难道==的语义不该看运行时类型吗?目前的处理方式貌似是因为是泛型参数,一律以iface看待,==也就会比较Value,显然不相等(因为Value.ptr不相等)

2. 不支持嵌套类型(也就是所谓内部类),导致entry的定义只能是

type entry struct {
	key   any
	value any
	next  *entry
}

  而不是

type entry[K comparable, V any] struct {
	key   K
	value V
	next  *entry[K, V]
}

因为后者的泛型参数K和HashMap的泛型参数K用的是两个命名空间,不能互相赋值。。。所以只能用any类型占位,赋值时再强转成HashMap的泛型类型K、V,十分别扭。

希望后续版本中能够改进。

上一篇:Go语言学习记录4——数组、切片和变参函数


下一篇:Go从入门到放弃之map(字典)