BitMap代码实现(go语言版本)

bitmap代码实现前置知识

在正式介绍代码实现前, 有必要先跟大家科普下关于bitmap的知识。

概念

Bitmap(位图)是一种数据结构,用于高效地表示和操作大量的布尔值或整数集合。位图利用位(bit)来表示元素是否存在,例如,用 1 表示存在,0 表示不存在,适合在需要处理大量数据且仅需简单存在性查询的场景中使用。

为什么要使用BitMap这种数据结构?

使用 Bitmap 的主要原因在于其内存效率高查询速度快,尤其在以下场景中特别有用:

  • 大规模数据存在性判断:Bitmap 可以节省大量内存来存储布尔状态或集合成员,例如用于黑名单、白名单、会员列表等场景。
  • 快速集合操作:位运算(如 AND、OR)可以同时对位图的多个值进行操作,快速完成集合间交集、并集的计算。
  • 压缩存储:当数据稀疏或重复较多时,位图可以通过稀疏位图或压缩位图进一步节省空间。

对比其他数据结构的优势

  • 内存高效:每个元素仅占用一位,而不是字节或更大的存储单位,因此在表示大量布尔数据时非常节省空间。
  • 快速查询和更新:通过索引直接访问某个位的值,查询和更新操作都是 O(1)。
  • 并行处理友好:Bitmap 操作可以利用并行位运算,如批量 AND、OR、XOR,实现高效的集合运算。
  • 简洁的布尔运算:适用于大规模数据的集合判断,如大数据处理中去重、去噪、集合合并等操作。

适用场景

  • 布隆过滤器(Bloom Filter):用于快速判断元素是否在集合中,常用于缓存、数据库等。
  • 数据去重:大规模去重场景下,使用位图表示已处理的数据,防止重复处理。
  • 图像处理:位图用于表示图像的像素点,常用于图像存储和压缩。
  • 权限控制:位图用于权限系统,快速标记用户权限或角色是否存在某权限。

示例

假设需要判断一个百万级用户列表中某个用户 ID 是否出现过。用普通的布尔数组可能会占用大量内存,而使用位图,可以用不到 1 MB 的空间来记录百万个用户的存在性,非常高效。

go代码实现

结构定义

type Bitmap struct {
	mp   map[string]int           // 存储连续的bitmap
	base int64                    // 每个int存储key的个数, 最多为64
	lock map[string]*sync.RWMutex // 读写锁, 支持并发安全
}

func NewBitmap(base int64) *Bitmap {
	return &Bitmap{
		mp:   make(map[string]int, 0),
		base: base,
		lock: make(map[string]*sync.RWMutex, 0),
	}
}

base决定一个int拿多少位来存储数据
mp为实际存储bitmap数据的字段, 它是一个键值对, key表示对一个数字对base作除法的商, value为一个存了base(最高为64)位的bitmap
lock 为每一个key配的一把读写锁, 保证并发读写安全

bitmap添加值

// Set 设置值
func (bm *Bitmap) Set(index string) error {

	// 创建一个新的 big.Int
	bi := new(big.Int)
	mod := new(big.Int)
	quotient := new(big.Int)

	// 将字符串转换为 big.Int
	_, success := bi.SetString(index, 10)
	if !success {
		return errors.New("the index is not a number")
	}

	// 对index作除法, 取得key, 即存放在bm字段的位置
	quotient.Div(bi, big.NewInt(bm.base))

	// 检查锁是否存在, 如果不存在则创建一个新的读写锁, 并赋值给当前的key值
	if _, ok := bm.lock[quotient.String()]; !ok {
		bm.lock[quotient.String()] = new(sync.RWMutex)
	}

	// 上锁操作
	bm.lock[quotient.String()].Lock()
	defer bm.lock[quotient.String()].Unlock()

	// 对index取模
	mod.Mod(bi, big.NewInt(int64(bm.base)))
	if !mod.IsInt64() {
		return errors.New("cant not set this index")
	}
	intMod := mod.Int64()

	// 存储到bitmap中
	bm.mp[quotient.String()] |= 1 << intMod
	return nil
}

使用golang官方提供的math/big包可以对高精度数字进行操作
对数字取商做key, 取模算出该数字在bitmap中对应的位置, 并通过bm.mp[quotient.String()] |= 1 << intMod将值存入其中

bitmap判断值存不存在

// Get 判断该值是否存在
func (bm *Bitmap) Get(index string) (bool, error) {

	// 创建一个新的 big.Int
	bi := new(big.Int)
	mod := new(big.Int)
	quotient := new(big.Int)

	// 将字符串转换为 big.Int
	_, success := bi.SetString(index, 10)
	if !success {
		return false, errors.New("the index is not a number")
	}

	quotient.Div(bi, big.NewInt(bm.base))

	if _, ok := bm.lock[quotient.String()]; !ok {
		bm.lock[quotient.String()] = new(sync.RWMutex)
	}

	bm.lock[quotient.String()].RLock()
	defer bm.lock[quotient.String()].RUnlock()

	mod.Mod(bi, big.NewInt(int64(bm.base)))
	if !mod.IsInt64() {
		return false, errors.New("cant not set this index")
	}
	intMod := mod.Int64()

	return (bm.mp[quotient.String()] & (1 << intMod)) != 0, nil
}

两个bitmap取交集

对bitmap取交集的操作在很多地方都会用到, 如搜索多个关键词, 取这几个关键词的交集, 本文章也实现了对bitmap取交集的函数, 具体代码如下

// Intersect 将会取bm跟bm2的交集, 需保证base相同
func (bm *Bitmap) Intersect(bm2 *Bitmap) *Bitmap {
	res := NewBitmap(bm.base)

	for s, v1 := range bm.mp {
		bm.lock[s].RLock()

		if v2, ok := bm2.mp[s]; ok {
			bm2.lock[s].RLock()
			res.lock[s] = new(sync.RWMutex)
			res.mp[s] = v1 & v2
			bm2.lock[s].RUnlock()
		}
		bm.lock[s].RUnlock()
	}

	return res
}

通过遍历第一个bitmap的key值, 判断第二个bitmap是否存在该key, 如果存在, 对两个数做与运算就能得出它们的交集, 计算时间为0(1),遍历key值的复杂度为o(n), 整体复杂度为o(n), 速度还是挺快的, 这也是bitmap的优势所在

实现迭代器便于对bitmap进行遍历

// 创建bitmap迭代器
func (bm *Bitmap) CreateBitMapIterator() *BitMapIterator {
	keys := maps.Keys(bm.mp)
	sort.Strings(keys)

	return &BitMapIterator{
		bitmap: bm,
		keys:   keys,
		idx:    0,
	}
}

// bitmap迭代器定义
type BitMapIterator struct {
	bitmap *Bitmap
	keys   []string
	idx    int
	count  int
}

// 返回结果定义
type BitMapResult struct {
	Data string
}

func (iter *BitMapIterator) Next() *BitMapResult {
	if iter.idx >= len(iter.keys) {
		return nil
	}

	// 初始化赋值
	if iter.count == 0 {
		iter.count = iter.bitmap.mp[iter.keys[iter.idx]]

		// 处理为0的情况
		if iter.count == 0 {
			iter.idx++
			return iter.Next()
		}
	}

	iter.bitmap.lock[iter.keys[iter.idx]].RLock()
	defer iter.bitmap.lock[iter.keys[iter.idx]].RUnlock()

	res := new(big.Int)

	quotient := new(big.Int)

	quotient.SetString(iter.keys[iter.idx], 10)
	res.Mul(quotient, big.NewInt(iter.bitmap.base))

	mod := int64(bits.TrailingZeros(uint(iter.count)))

	res.Add(res, big.NewInt(mod))
	iter.count -= 1 << mod

	if iter.count == 0 {
		iter.idx++
	}

	return &BitMapResult{
		Data: res.String(),
	}
}

总结

Bitmap 是一种空间效率高、查询效率快的数据结构,特别适合海量布尔数据的存储与集合操作。本实现采用高精度数处理、大规模并行操作与并发控制的设计,既支持超大数据集,又保证了数据的并发安全性,非常适合在大数据处理、权限控制、去重等应用场景中使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ambition!6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值