跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

怎么在业务系统中使用位运算代替 Set 以极大地降低存储空间

EnumSet本质是位运算但有封装开销,高频场景下直接用long位图更高效;枚举用ordinal()映射位索引,运行时安全,持久化需显式指定bitIndex避免序号变更风险。 为什么 EnumSet 已经够快,还要手动位运算? 因为
EnumSet
本质就是位运算,但它封装了操作、做了类型安全检查、返回的是集合接口——这些在高频调用或嵌入式/协议层场景中仍是冗余。如果你的业务只需要「存几个标志位 + 快速判断」,且枚举值 ≤64 个,直接用
long
存储位图,比
EnumSet
少一次对象分配、少一层接口抽象、序列化时也更紧凑(比如写进 Redis 或 Kafka 消息体)。 怎么把枚举映射到位图索引上? 关键不是手算二进制,而是复用枚举的
ordinal()
—— 它天然连续、从 0 开始,正好对应位位置。但要注意: 不能直接依赖
ordinal()
做持久化存储 ,因为枚举成员增删会改变序号;只应在运行时内存计算中使用。 定义枚举时显式指定值(可选),避免意外变更影响:
enum FeatureFlag { LOGIN(0), PAY(1), SHARE(2), NOTIFICATION(3); private final int bitIndex; FeatureFlag(int bitIndex) { this.bitIndex = bitIndex; } }
设置某标志:
flags |= (1L
判断是否启用:
(flags & (1L
清除标志:
flags &= ~(1L
MySQL / JSON / RPC 中怎么传这个位图? 位图本质是整数,传输和存储最省事: MySQL:字段用
TINYINT
(≤8 位)、
SMALLINT
(≤16)、
INT
(≤32)或
BIGINT
(≤64),别用
ENUM
或
SET
——它们底层也是整数,但语义固化、扩展差 JSON:直接序列化为数字,如
{"features": 5}
(二进制
101
表示第 0 和第 2 位开启) RPC(如 gRPC):定义
int64 features_mask = 1;
字段,比 repeated enum 更轻量 注意:Java 反序列化时,需确保枚举定义顺序与写入时一致,否则
ordinal()
错位 → 位含义错乱 容易踩的坑:越界、符号、跨语言兼容性 位运算看着简单,出错往往静默且难排查:
1 在 int 上会溢出,务必用 1L (long)并确认 n
Java 中
long
是有符号的,但位运算是按补码逻辑走的,只要不参与算术比较(比如
mask > 0
),就别担心负数问题 Go / Rust / C# 的枚举
ordinal
行为可能不同;跨语言通信时,建议在文档里明确定义「bit 0 对应 FeatureFlag.LOGIN」,而不是依赖语言默认序号 如果枚举值超过 64 个,
long
不够用,必须切分多个字段或改用
byte[]
,这时再硬搞位运算收益递减,不如退回
EnumSet
或数据库
SET
类型 实际业务中,真正需要手动位运算的场景不多——多数时候
EnumSet
已经是最佳选择。只有当你明确卡在 GC 压力、序列化体积或跨系统位对齐要求上时,才值得把控制权收回来。而一旦收回来,就要对每个 bit 的生命周期负责。

相关文章