Go 实现set,位图 和 map[interface{}]struct{} 哪种方式好呢?

536 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

引言

在golang中没有内置的set,我们可以使用以下两种方式实现

  • 位图:数组,二进制位表示一个数是否存在。例如Byte 0-7位可以表示8个连续数字是否出现。
  • map:map[interface{}]bool或者map[interface{}]struct{}实现。

本文主要介绍这两种实现方式

1.位图(数组+位运算)

适用于集合中数字最大值确定的情况。

对于一个数据集合nums,若其最大值为max,一个Byte可以表示8个数,那么可以用max/8 +1 个byte表示0-max的数是否存在。

package main

import "fmt"

// BitMap 位图
type BitMap struct {
   bits []byte
   max  int
}

// NewBitMap 初始化一个BitMap
// 一个byte有8位,可代表8个数字,max取模后加1为存放最大数所需的容量
// >>3等价于除以8
func NewBitMap(max int) *BitMap {
   bits := make([]byte, (max>>3)+1)
   return &BitMap{bits: bits, max: max}
}

// Add 添加一个数字到位图
//计算添加数字在数组中的索引index,一个索引可以存放8个数字
//计算存放到索引下的第几个位置,一共0-7个位置
//原索引下的内容与1左移到指定位置后做或运算
func (b *BitMap) Add(num uint) {
   index := num >> 3
   pos := num & 0x07
   b.bits[index] |= 1 << pos
}

// Contains 判断一个数字是否在位图
//找到数字所在的位置,然后做与运算
func (b *BitMap) Contains(num uint) bool {
   index := num >> 3
   pos := num & 0x07
   return b.bits[index]&(1<<pos) != 0
}

// Remove 删除一个数字在位图
//找到数字所在的位置取反,然后与索引下的数字做与运算
func (b *BitMap) Remove(num uint) {
   index := num >> 3
   pos := num & 0x07
   b.bits[index] = b.bits[index] & ^(1 << pos)
}

// Max 位图的最大数字
func (b *BitMap) Max() int {
   return b.max
}

func (b *BitMap) String() string {
   return fmt.Sprint(b.bits)
}

func main() {
   bitmap := NewBitMap(32)
   bitmap.Add(1)
   bitmap.Add(3)
   bitmap.Add(5)
   fmt.Println(bitmap.String())
}

2.map[interface{}]struct{}实现

使用map[interface{}]struct{}实现,可以支持任意类型,并且struct{}不占内存。

但是我个人觉得没有必要做set结构的封装,其本质还是使用map进行实现,所以推荐大家在使用时还是直接使用map[interface{}]struct{},set需要的操作用map完全够用。

package main

import (
   "fmt"
   "unsafe"
)

//使用空结构体大小0,不占内存
type exists struct{}

//使用空接口interface{},支持任意类型元素
type set struct {
   m map[interface{}]exists
}

// NewSet 调用Add函数添加多个元素到set中
func NewSet(items ...interface{}) *set {
   s := &set{}
   s.m = make(map[interface{}]exists)
   s.Add(items...)
   return s
}

// Add 将元素列表插入到set中
func (s *set) Add(items ...interface{}) {
   for _, item := range items {
      s.m[item] = exists{}
   }
}

// Remove 删除指定元素
func (s *set) Remove(item interface{}) {
   delete(s.m, item)
}

// Contains 查询指定元素是否存在
func (s *set) Contains(item interface{}) bool {
   _, ok := s.m[item]
   return ok
}

// Size 查询集合大小
func (s *set) Size() int {
   return len(s.m)
}


func main() {
   s1 := struct{}{}
   s2 := struct{}{}
   fmt.Printf("s1 address:%p, s2 address:%p\n", &s1, &s2) //地址一样
   fmt.Println(s1 == s2) //true

   fmt.Println(unsafe.Sizeof(struct{}{})) //0

   s := NewSet(8, "beijing")
   arr := [5]int {1,2,3,4,5}
   s.Add(arr)
   s.Add(false)
   fmt.Println(s.Contains(false)) //true
   fmt.Println(s.Contains(8)) //true
   fmt.Println(s.Contains(arr)) //true

}