当前位置:首页 > 文章列表 > Golang > Go教程 > Go bitset 怎样把位集合编码为二进制数据

Go bitset 怎样把位集合编码为二进制数据

来源:17golang原创 2026-10-31 09:08:07 0浏览 收藏

Go bitset 怎样把位集合编码为二进制数据

使用 github.com/bits-and-blooms/bitset 时,可以调用 MarshalBinary() 得到二进制字节,再用另一个 BitSet 的 UnmarshalBinary(data) 恢复;需要直接对接 io.Writer、io.Reader 时使用 WriteTo、ReadFrom。不要把内部字切片或打印出来的集合文本当成同一种二进制格式。

项目官方地址:https://github.com/bits-and-blooms/bitset。下面以官方接口文档为依据,按一个假设的“编码后恢复不一致”场景展开。它不是线上事故记录,也没有声称代码已经在本机运行;第一人称部分表达的是我的实现取舍。

先把恢复异常当成格式问题排查

假设一个服务先把位集合落盘,重启后发现成员或长度不符合预期。我会先保留原数据副本,确认写入端到底保存了什么:库的二进制编码、uint64内部字数组,还是字符串展示。只有知道格式,才能选择正确的读法。

排查顺序可以从写入代码、传输封装、读取代码依次追踪,但不必先假设位运算算法出错。比如写入端只保存若干机器字,而读取端按库的完整格式解释,数据开头的含义就已经不同;再调整位索引无法修复这种格式错配。

为什么内部字数组不等于完整编码

官方 WriteTo 文档描述的格式包含一个 uint64 表达的位长度,后接承载位状态的 uint64字数组;MarshalBinary 按这套序列化接口提供字节结果。这里的长度是位集合的逻辑长度,不是置1位的数量。

逻辑位长度和字数组组成二进制载荷的静态结构
结构图:完整编码保留位长度和字数据,内部字切片与持久化格式不能混为一谈。

我会把三个量分开记录:Len是位长度,Count是置1位数量,len(payload)是编码字节数。它们有不同单位,不能拿Count推断应该读取多少字节。尾部全0位仍可能属于逻辑长度约定,因此只知道哪些位为1不足以替代所有元数据。

Words提供内部uint64字切片的直接访问,不是完整的字节编码副本;修改它会影响位集合。除非另行定义了格式、长度和所有权规则,我不会将它作为普通业务的持久化入口。

用MarshalBinary编码并恢复到新对象

下面的原创示例创建逻辑长度96的位集合,设置三个位置,编码后恢复到新对象。将其放入已经配置该依赖的Go模块即可。示例在读取错误时返回,并明确比较逻辑长度与集合成员,不先覆盖现有业务状态。

package main

import (
    "fmt"
    "os"

    "github.com/bits-and-blooms/bitset"
)

func run() error {
    original := bitset.New(96)
    original.Set(2).Set(31).Set(80)
    // 使用库的二进制格式,不直接导出内部字切片。
    payload, err := original.MarshalBinary()
    if err != nil {
        return fmt.Errorf("编码位集合: %w", err)
    }
    // 解码到新对象;失败时不把半成品替换进业务状态。
    var restored bitset.BitSet
    if err := restored.UnmarshalBinary(payload); err != nil {
        return fmt.Errorf("恢复位集合: %w", err)
    }
    if restored.Len() != original.Len() || !restored.Equal(original) {
        return fmt.Errorf("恢复后的长度或成员不一致")
    }
    // 数量、逻辑长度与编码字节数是不同维度。
    fmt.Printf("len=%d count=%d bytes=%d\n",
        restored.Len(), restored.Count(), len(payload))
    fmt.Println("位置80:", restored.Test(80))
    return nil
}

func main() {
    if err := run(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

输入决定置1位数量为3,位置80应该存在;示例会打印实际编码长度,但本文不伪造运行输出。代码中的检查是读者应用自身的往返处理示例,不代表只要长度一致就能证明外部存储完整可靠。

MarshalBinary返回error,UnmarshalBinary也返回error,都应检查。对解码失败的临时对象,我的选择是直接丢弃,不让业务继续使用不完整结果。将错误连同所处阶段返回,也比只给调用方一个“加载失败”更容易定位。

改成流接口时怎样处理错误

如果手上已经是流,可以直接调用WriteTo与ReadFrom,避免业务先手动拼装机器字。下方函数需要在导入列表中补充 "bytes",其余fmt和bitset导入与前例相同。

// StreamRoundTrip 演示内存流,不涉及文件句柄或网络连接。
func StreamRoundTrip(source *bitset.BitSet) (*bitset.BitSet, error) {
    if source == nil {
        return nil, fmt.Errorf("源位集合不能为nil")
    }
    var buf bytes.Buffer
    if _, err := source.WriteTo(&buf); err != nil {
        return nil, fmt.Errorf("写入位集合流: %w", err)
    }
    restored := bitset.New(0)
    if _, err := restored.ReadFrom(&buf); err != nil {
        return nil, fmt.Errorf("读取位集合流: %w", err)
    }
    return restored, nil
}
二进制接口、流接口和读取约定之间的静态关系
说明图:两组接口服务于字节与流两种使用方式,读写两端仍需遵守相同格式和字节序。

内存缓冲区不需要Close。改成文件或连接时,资源由创建它的调用方负责关闭;若再包一层bufio.Writer,还要处理Flush错误。序列化方法返回成功,不能自动代表文件缓冲已刷新,或远端已持久保存。

我会保留的持久化约定

约定建议避免的误解
字节序沿用库默认大端序,读写保持一致一端切小端,另一端继续按大端读
格式身份记录库版本与应用封装版本任何字数组都能交给UnmarshalBinary
记录边界按存储或传输协议定义每条载荷单个字节切片自动解决业务多记录 framing
状态替换先恢复临时对象,成功后交给业务解码失败后继续使用半成品

该库提供BigEndian、LittleEndian等包级设置。采用默认值更省去额外协调;如果历史数据使用了不同约定,应依据真实格式处理,不要并发切换全局设置来猜哪一种结果“看起来正常”。

能把二进制字节直接当文本保存吗?如果通道只接受文本,应在库二进制之外再明确选择Base64等文本封装,读取时先解除封装,再交给二进制解码器。文本封装不能与库自身的其他序列化接口随意混搭。

这种编码能代替输入规模限制吗?不能。面向外部数据时,应根据业务范围限制记录大小和允许的逻辑长度;错误返回也不能替代资源预算。本文的小集合示例只说明编码和恢复关系。

对普通业务,我会优先使用库提供的二进制往返接口,把原始字节、字节序和逻辑长度这些含义说明白。这样遇到恢复异常时,可以沿着格式契约查找,而不必先怀疑集合运算本身。

参考:MarshalBinary接口;WriteTo格式说明。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go bitset 怎样枚举所有置位的索引Go bitset 怎样枚举所有置位的索引
上一篇
Go bitset 怎样枚举所有置位的索引
Go bitset 怎样对两组标记计算对称差
下一篇
Go bitset 怎样对两组标记计算对称差
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    808次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    798次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    888次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    860次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    686次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码