Golang中int/bitmap逻辑实现探讨

Golang中int/bitmap逻辑实现探讨 我正在寻找一种巧妙的方法,将三个整数代码及其可能的组合“映射”到一个字段中。

想象一下类似颜色的东西: 红色 = 1 绿色 = 2 蓝色 = 3

每种颜色都可以被选择。我不想使用三个布尔标志,因为我预计数据量会很大(我想将其记录为事件)

我不能简单地将这些值相加或相乘,因为 1 + 2 = 3(哈哈,这很明显),所以我无法区分是红色和绿色,还是仅仅是蓝色——希望你明白我的意思。

这并不完全是一个 Go 语言的问题,而更像是一般的逻辑问题。出于性能考虑,我也想避免通过整数/字符串转换来“构建”一个新数字,例如“123”或“12”。

我在考虑位图/位逻辑之类的方案……不太确定——有什么想法吗?


更多关于Golang中int/bitmap逻辑实现探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

好的,原来不知道该怎么搜索,是“按位”和 &。

没想到 &,因为它是指针/地址运算符。

更多关于Golang中int/bitmap逻辑实现探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好!

你可以使用 uint8 类型来存储颜色值,例如用 11000000 表示红色,00110000 表示蓝色,00001100 表示绿色。然后,你可以通过按位或(bitwise OR)等操作来组合颜色,比如红蓝交集、蓝绿交集、绿红交集等等……但是,如果你真的想要覆盖从 RGBA(0,0,0,0) 到 RGBA(255,255,255,255) 的整个范围,你将需要 8 位 X 4 通道,也就是总共 4 个字节来存储所有 RGBA 值。在这种情况下,使用 uint32 类型来存储十六进制值会是更好的选择。希望这能讲得通。

这里可以找到一些按位运算的示例:

图片

位掩码、位集和标志位

代码示例 位掩码或位集是一组布尔值(通常称为标志位),由一个或多个数字中的比特位表示。

这是一个典型的位标志(bit flags)应用场景,非常适合使用位运算来实现。在Go中,可以通过定义常量并使用位掩码来高效地表示和操作这些组合。

解决方案:使用位标志

package main

import "fmt"

// 定义颜色标志,使用 1 左移不同的位数来确保每个标志占用独立的位
const (
    Red   = 1 << iota // 1 << 0 = 1 (二进制: 0001)
    Green             // 1 << 1 = 2 (二进制: 0010)
    Blue              // 1 << 2 = 4 (二进制: 0100)
)

type ColorSet uint8

// 添加颜色
func (c *ColorSet) Add(color int) {
    *c |= ColorSet(color)
}

// 移除颜色
func (c *ColorSet) Remove(color int) {
    *c &^= ColorSet(color)
}

// 检查是否包含颜色
func (c ColorSet) Has(color int) bool {
    return c&ColorSet(color) != 0
}

// 获取所有颜色
func (c ColorSet) Colors() []string {
    var colors []string
    if c.Has(Red) {
        colors = append(colors, "Red")
    }
    if c.Has(Green) {
        colors = append(colors, "Green")
    }
    if c.Has(Blue) {
        colors = append(colors, "Blue")
    }
    return colors
}

func main() {
    var colors ColorSet
    
    // 添加红色和绿色
    colors.Add(Red)
    colors.Add(Green)
    
    fmt.Printf("二进制表示: %04b\n", colors) // 输出: 0011
    fmt.Printf("十进制值: %d\n", colors)    // 输出: 3
    fmt.Printf("包含的颜色: %v\n", colors.Colors()) // 输出: [Red Green]
    
    // 检查特定颜色
    fmt.Printf("包含红色? %v\n", colors.Has(Red))   // 输出: true
    fmt.Printf("包含蓝色? %v\n", colors.Has(Blue))  // 输出: false
    
    // 添加蓝色
    colors.Add(Blue)
    fmt.Printf("添加蓝色后: %04b\n", colors) // 输出: 0111
    fmt.Printf("十进制值: %d\n", colors)      // 输出: 7
    
    // 移除绿色
    colors.Remove(Green)
    fmt.Printf("移除绿色后: %04b\n", colors) // 输出: 0101
    fmt.Printf("十进制值: %d\n", colors)      // 输出: 5
}

扩展示例:支持更多颜色和组合检查

package main

import "fmt"

const (
    Red    = 1 << iota // 1
    Green              // 2
    Blue               // 4
    Yellow             // 8
    Purple             // 16
    // 可以继续添加,最多支持 64 种(如果使用 uint64)
)

type ColorFlags uint32

// 预定义一些常用组合
const (
    RGB      = Red | Green | Blue          // 7
    Primary  = Red | Blue | Yellow         // 13
    All      = Red | Green | Blue | Yellow | Purple // 31
)

func main() {
    // 创建不同的颜色组合
    var combo1 ColorFlags = Red | Green      // 红色+绿色: 3
    var combo2 ColorFlags = Blue             // 蓝色: 4
    var combo3 ColorFlags = Red | Green | Blue // RGB: 7
    
    fmt.Printf("combo1: %05b (值: %d)\n", combo1, combo1)
    fmt.Printf("combo2: %05b (值: %d)\n", combo2, combo2)
    fmt.Printf("combo3: %05b (值: %d)\n", combo3, combo3)
    
    // 检查是否是特定组合的子集
    fmt.Printf("combo1 是 RGB 的子集? %v\n", combo1&RGB == combo1) // true
    fmt.Printf("combo2 包含红色? %v\n", combo2&Red != 0)           // false
    
    // 合并两个组合
    merged := combo1 | combo2
    fmt.Printf("合并后: %05b (值: %d)\n", merged, merged) // 输出: 00111 (7)
    
    // 检查交集
    intersection := combo1 & combo3
    fmt.Printf("combo1 和 combo3 的交集: %05b\n", intersection)
}

存储和检索示例

package main

import (
    "fmt"
    "math/rand"
)

const (
    Red    = 1 << iota
    Green
    Blue
)

// 模拟事件记录
type Event struct {
    ID     int
    Colors uint8 // 只需要1个字节存储颜色组合
}

func main() {
    // 模拟大量事件数据
    events := make([]Event, 1000)
    
    for i := range events {
        events[i].ID = i
        // 随机分配颜色组合
        colors := uint8(0)
        if rand.Intn(2) == 1 {
            colors |= Red
        }
        if rand.Intn(2) == 1 {
            colors |= Green
        }
        if rand.Intn(2) == 1 {
            colors |= Blue
        }
        events[i].Colors = colors
    }
    
    // 查询包含红色的所有事件
    var redEvents []int
    for _, event := range events {
        if event.Colors&Red != 0 {
            redEvents = append(redEvents, event.ID)
        }
    }
    
    fmt.Printf("总事件数: %d\n", len(events))
    fmt.Printf("包含红色的事件数: %d\n", len(redEvents))
    
    // 显示前10个事件的颜色组合
    fmt.Println("\n前10个事件的颜色组合:")
    for i := 0; i < 10 && i < len(events); i++ {
        fmt.Printf("事件 %d: %03b (Red:%v, Green:%v, Blue:%v)\n",
            events[i].ID,
            events[i].Colors,
            events[i].Colors&Red != 0,
            events[i].Colors&Green != 0,
            events[i].Colors&Blue != 0,
        )
    }
}

性能优势

  1. 存储高效:只需要1个字节(uint8)就可以存储8种不同的标志,uint16可以存储16种,以此类推
  2. 查询快速:位运算(&, |, ^, &^)是CPU原生支持的最快操作之一
  3. 内存紧凑:大量数据时显著减少内存占用
  4. 操作原子性:单个标志的读写是原子的

这种方法完美解决了你的问题:每个颜色都有唯一的位位置,组合通过位或运算实现,不会出现1+2=3的冲突情况。

回到顶部