哈希表 Map Golang实现,使用红黑树和AVL树-性能爆表-非递归版本
golang map
键key值value
Map
GolangMapMap

使用平衡二叉查找树结构实现的哈希表,不会占用多余空间,因为平衡树的缘故,查找元素效率也不是那么地糟糕,所以有时候选择它来做哈希表是特别好的。

如何使用

很简单,执行:

go get -v github.com/hunterhug/gomap
gomap

有以下几种用法:

Red-Black Treegomap.New()gomap.NewMap()gomap.NewRBMap()AVL Treegomap.NewAVLMap()

核心 API:

// Map 实现
// 被设计为并发安全的
type Map interface {
	Put(key string, value interface{})            // 放入键值对
	Delete(key string)                            // 删除键
	Get(key string) (interface{}, bool)           // 获取键,返回的值value是interface{}类型的,想返回具体类型的值参考下面的方法
	GetInt(key string) (int, bool, error)         // get value auto change to Int
	GetInt64(key string) (int64, bool, error)     // get value auto change to Int64
	GetString(key string) (string, bool, error)   // get value auto change to string
	GetFloat64(key string) (float64, bool, error) // get value auto change to string
	GetBytes(key string) ([]byte, bool, error)    // get value auto change to []byte
	Contain(key string) bool                      // 查看键是否存在
	Len() int64                                   // 查看键值对数量
	KeyList() []string                            // 根据树的层次遍历,获取键列表
	KeySortedList() []string                      // 根据树的中序遍历,获取字母序排序的键列表
	Iterator() MapIterator                        // 迭代器,实现迭代
	SetComparator(comparator)                     // 可自定义键比较器,默认按照字母序
}

// Iterator 迭代器,不是并发安全,迭代的时候确保不会修改Map,否则可能panic或产生副作用
type MapIterator interface {
	HasNext() bool // 是否有下一对键值对
	Next() (key string, value interface{}) // 获取下一对键值对,迭代器向前一步
}
Golangkeyvalue

算法比较

Red-Black Tree2log(N+1)
AVL Tree1.44log(N+2)-1.328
AVL Tree
Java/C#/C++Red-Black TreeWindowsAVL Tree

例子

下面是一个基本的例子:

package main

import (
	"fmt"
	"github.com/hunterhug/gomap"
	"math/rand"
	"time"
)

// 循环的次数,用于随机添加和删除键值对
// 可以修改成1000
var num = 10

func init() {
	// 随机数种子初始化
	rand.Seed(time.Now().Unix())
}

func main() {
	// 1. 新建一个 Map,默认为标准红黑树实现
	m := gomap.New()
	for i := 0; i < num; i++ {
		key := fmt.Sprintf("%d", rand.Int63n(int64(num)))
		fmt.Println("add key:", key)
		// 2. 放键值对
		m.Put(key, key)
	}

	fmt.Println("map len is ", m.Len())

	// 3. 迭代器使用
	iterator := m.Iterator()
	for iterator.HasNext() {
		k, v := iterator.Next()
		fmt.Printf("Iterator key:%s,value %v\n", k, v)
	}

	// 4. 获取键中的值
	key := "9"
	value, exist := m.Get(key)
	if exist {
		fmt.Printf("%s exist, value is:%s\n", key, value)
	} else {
		fmt.Printf("%s not exist\n", key)
	}

	// 5. 获取键中的值,并且指定类型,因为类型是字符串,所以转成整形会报错
	_, _, err := m.GetInt(key)
	if err != nil {
		fmt.Println(err.Error())
	}

	// 6. 内部辅助,检查是不是一颗正常的红黑树
	if m.Check() {
		fmt.Println("is a rb tree,len:", m.Len())
	}

	// 7. 删除键 ‘9‘ 然后再找 ‘9‘
	m.Delete(key)
	value, exist = m.Get(key)
	if exist {
		fmt.Printf("%s exist, value is:%s\n", key, value)
	} else {
		fmt.Printf("%s not exist\n", key)
	}

	// 8. 删除很多键值对
	for i := 0; i < num; i++ {
		key := fmt.Sprintf("%d", rand.Int63n(int64(num)))
		fmt.Println("delete key:", key)
		m.Delete(key)
	}

	// 9. 获取键列表
	fmt.Printf("keyList:%#v,len:%d\n", m.KeyList(), m.Len())
	fmt.Printf("keySortList:%#v,len:%d\n", m.KeySortedList(), m.Len())

	// 10. 再次检查是否是一颗正常的红黑树
	if m.Check() {
		fmt.Println("is a rb tree,len:", m.Len())
	}
}

性能测试

我进行了压测:

go test -run="bench_test.go" -test.bench=".*" -test.benchmem=1 -count=1

BenchmarkGolangMapPut-4                  1000000              1385 ns/op             145 B/op          6 allocs/op
BenchmarkRBTMapPut-4                      528231              3498 ns/op             113 B/op          6 allocs/op
BenchmarkAVLMapPut-4                     1000000              3317 ns/op             104 B/op          6 allocs/op
BenchmarkAVLRecursionMapPut-4             389806              4563 ns/op             116 B/op          6 allocs/op
BenchmarkGolangMapDelete-4               2630281               582 ns/op              15 B/op          1 allocs/op
BenchmarkRBTMapDelete-4                  2127256               624 ns/op              15 B/op          1 allocs/op
BenchmarkAVLMapDelete-4                   638918              2256 ns/op              15 B/op          1 allocs/op
BenchmarkAVLRecursionMapDelete-4          376202              2813 ns/op              15 B/op          1 allocs/op
BenchmarkGolangMapGet-4                  9768266               172 ns/op               2 B/op          1 allocs/op
BenchmarkRBTMapGet-4                     3276406               352 ns/op               2 B/op          1 allocs/op
BenchmarkAVLMapGet-4                     3724939               315 ns/op               2 B/op          1 allocs/op
BenchmarkAVLRecursionMapGet-4            2550055               462 ns/op               2 B/op          1 allocs/op
BenchmarkGolangMapRandom-4               1000000              2292 ns/op             163 B/op          8 allocs/op
BenchmarkRBTMapRandom-4                   244311              4635 ns/op             136 B/op          8 allocs/op
BenchmarkAVLMapRandom-4                   488001              5879 ns/op             132 B/op          8 allocs/op
BenchmarkAVLRecursionMapRandom-4          211246              5411 ns/op             138 B/op          8 allocs/op
Mapgolang mapMap

空间换时间,还是时间换空间,这是一个问题。