一个泛型的有序Go Map实现

我们知道, Go内建的map类型对于插入的元素并没有保持它们的插入顺序,遍历的时候也故意设置成随机的。因此,如果我们想让map保持元素的插入顺序,需要借助第三方的库才行,今天就给大家介绍一个这样的库OrderedMap

其实在其他编程语言中,也有类似的数据结构,比如java中的 LinkedHashMap, python中的OrderedDict

本文介绍如何使用Go语言实现这样的一种数据类型。注意我们要实现的是OrderedMap, 不是SortedMap或者TreeMap,SortedMap遍历的时候是按照Key的排序顺序遍历的,我们可以通过先获取所有的key并排序,再逐个访问对应的值。

但是OrderedMap遍历的时候要是保持插入的顺序,这怎么办到的呢?

我们看看OrderedMap的功能,既要有HashMap的功能,可以快速得到一个键值对,又要保持插入顺序,那么我们可以通过组合的方式实现。

  • HashMap的功能: 通过内建的map实现
  • 保持插入顺序: 本来可以通过container/list实现,但是为了支持泛型,我们使用 github.com/smallnest/exp/container/list实现。

下面就是OrderedMap数据结构,它包含entrieslist两个字段。entries是一个map,它的值类型是*Entry; list中的元素是*Entry,和map中的值指向同一个元素:

1
2
3
4
type OrderedMap[K comparable, V any] struct {
entries map[K]*Entry[K, V]
list *list.List[*Entry[K, V]]
}

其中Entry的定义如下,除了包含Key和Value值,它还包含一个list的Element元素,这样它就实现了一个双线链表,每个Entry都可以找到对它的前驱和后继:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Entry is a key-value pair in OrderedMap.
type Entry[K comparable, V any] struct {
Key K
Value V
element *list.Element[*Entry[K, V]]
}
// Next returns a pointer to the next entry.
func (e *Entry[K, V]) Next() *Entry[K, V] {
entry := e.element.Next()
if entry == nil {
return nil
}
return entry.Value
}
// Prev returns a pointer to the previous entry.
func (e *Entry[K, V]) Prev() *Entry[K, V] {
entry := e.element.Prev()
if entry == nil {
return nil
}
return entry.Value
}

增加和删除的时候,我们就需要对这两个字段都进行操作,才能保持一致性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func (m *OrderedMap[K, V]) Set(key K, value V) (val V, existed bool) {
if entry, existed := m.entries[key]; existed { // 如果key存在,就是更新
oldValue := entry.Value
entry.Value = value
return oldValue, true
}
entry := &Entry[K, V]{
Key: key,
Value: value,
}
entry.element = m.list.PushBack(entry) // 加入到链表
m.entries[key] = entry // 加入到map中
return value, false
}
func (m *OrderedMap[K, V]) Delete(key K) (val V, existed bool) {
if entry, existed := m.entries[key]; existed { // 如果存在
m.list.Remove(entry.element) // 从链表中移除
delete(m.entries, key) // 从map中删除
return entry.Value, true
}
return
}

查找的时候直接查找map就可以了,性能没有损失:

1
2
3
4
5
6
7
func (m *OrderedMap[K, V]) Get(key K) (val V, existed bool) {
if entry, existed := m.entries[key]; existed {
return entry.Value, true
}
return
}

遍历的时候,遍历链表结构,因为它保持了插入顺序:

1
2
3
4
5
6
7
8
9
10
func (m *OrderedMap[K, V]) Range(f func(key K, value V) bool) {
list := m.list
for e := list.Front(); e != nil; e = e.Next() {
if e.Value != nil {
if ok := f(e.Value.Key, e.Value.Value); !ok {
return
}
}
}
}

这是这个数据结构最主要的方法,当然它还提供更多的方法,比如最老值、最新值、随机遍历等,如果你有相应的需求场景,可以考虑使用它。