note4cs/技术/Go/Go实现BitMap.md
2025-03-25 17:59:53 +08:00

5.3 KiB
Raw Blame History

在 Go 语言中,uint64 是一种无符号整数类型,占用 64 位bit。每一位可以存储一个布尔值0 或 1。以下是 bituint64 之间的对应关系及操作的详细解释:


1. uint64 的结构

  • 一个 uint64 类型的变量由 64 个二进制位组成,例如:
    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    
    每一位从右到左编号为 0 到 63最低位是第 0 位,最高位是第 63 位)。

2. 如何定位某个位

假设我们要操作第 pos 位(从 0 开始计数),可以通过以下步骤确定其在 uint64 中的位置:

(1) 计算所在 uint64 的索引

  • 如果位图使用多个 uint64 来存储数据,则需要先确定目标位所在的 uint64 索引。
  • 公式为:
    index = pos / 64
    
    • pos / 64 表示将位的位置除以 64得到它属于哪个 uint64

(2) 计算具体位偏移量

  • 在确定了目标 uint64 后,需要进一步确定该位在当前 uint64 中的具体位置。
  • 公式为:
    offset = pos % 64
    
    • pos % 64 表示取余运算,得到目标位在当前 uint64 中的偏移量(范围为 0 到 63

3. 位操作的实现

Go 语言提供了位运算符来操作单个位。以下是常见的位操作及其作用:

(1) 设置某一位为 1

  • 使用按位或运算符 | 和左移运算符 <<
    b.bits[pos/64] |= 1 << uint(pos%64)
    
    • 1 << uint(pos%64):将数字 1 左移 offset 位,生成一个只有目标位为 1 的掩码。
    • |=:将掩码与当前 uint64 值进行按位或运算,确保目标位被设置为 1。

(2) 清除某一位为 0

  • 使用按位与运算符 & 和按位取反运算符 ^
    b.bits[pos/64] &^= 1 << uint(pos%64)
    
    • 1 << uint(pos%64):生成一个只有目标位为 1 的掩码。
    • &^=:将掩码取反后与当前 uint64 值进行按位与运算,确保目标位被清除为 0。

(3) 检查某一位是否为 1

  • 使用按位与运算符 &
    return b.bits[pos/64]&(1<<uint(pos%64)) != 0
    
    • 1 << uint(pos%64):生成一个只有目标位为 1 的掩码。
    • &:将掩码与当前 uint64 值进行按位与运算,结果非零表示目标位为 1否则为 0。

4. 举例说明

假设我们有一个 uint64,初始值为 0

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

(1) 设置第 10 位为 1

  • 计算索引和偏移量:
    • index = 10 / 64 = 0
    • offset = 10 % 64 = 10
  • 执行操作:
    bits[0] |= 1 << 10
    
    结果:
    00000000 00000000 00000000 00000000 00000000 00000000 00000100 00000000
    

(2) 清除第 10 位为 0

  • 执行操作:
    bits[0] &^= 1 << 10
    
    结果:
    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    

(3) 检查第 10 位是否为 1

  • 执行操作:
    result := bits[0]&(1<<10) != 0
    
    • 如果第 10 位为 1resulttrue
    • 如果第 10 位为 0resultfalse

5. 总结

  • uint64 是一个 64 位的存储单元,每一位可以独立存储一个布尔值。
  • 通过 pos / 64 确定目标位所在的 uint64,通过 pos % 64 确定目标位在当前 uint64 中的具体位置。
  • 使用位运算符(如 |&^<<)可以高效地操作单个位。

6.代码实现

package main

import (
	"fmt"
)

// Bitmap 结构体表示位图
type Bitmap struct {
	bits []uint64 // 使用一个 uint64 切片存储位图数据,每个元素存储 64 位
	size int      // 位图的总大小(位数)
}

// NewBitmap 创建一个新的位图size为位图的大小
func NewBitmap(size int) *Bitmap {
	if size < 0 {
		size = 0
	}
	return &Bitmap{
		bits: make([]uint64, (size+63)/64), // 根据位图大小计算需要的 uint64 元素数量
		size: size,                         // 记录位图的总大小
	}
}

// SetBit 设置指定位置的位为1
func (b *Bitmap) SetBit(pos int) {
	if pos < 0 || pos >= b.size { // 检查位置是否越界
		return
	}
	idx := pos / 64
	offset := pos % 64
	// 使用 uint64(1) 避免可能的整数溢出问题
	b.bits[idx] |= uint64(1) << offset
}

// ClearBit 清除指定位置的位为0
func (b *Bitmap) ClearBit(pos int) {
	if pos < 0 || pos >= b.size { // 检查位置是否越界
		return
	}
	idx := pos / 64
	offset := pos % 64
	// 使用 uint64(1) 避免可能的整数溢出问题
	b.bits[idx] &^= uint64(1) << offset
}

// GetBit 获取指定位置的位
func (b *Bitmap) GetBit(pos int) bool {
	if pos < 0 || pos >= b.size { // 检查位置是否越界
		return false
	}
	idx := pos / 64
	offset := pos % 64
	// 使用 uint64(1) 避免可能的整数溢出问题
	return (b.bits[idx] & (uint64(1) << offset)) != 0
}

func main() {
	// 创建一个大小为 100 的位图
	bitmap := NewBitmap(100)

	// 设置第 10 位为 1
	bitmap.SetBit(10)

	// 检查第 10 位是否为 1并打印结果输出 true
	fmt.Println(bitmap.GetBit(10)) // 输出: true

	// 清除第 10 位为 0
	bitmap.ClearBit(10)

	// 再次检查第 10 位是否为 1并打印结果输出 false
	fmt.Println(bitmap.GetBit(10)) // 输出: false
}