对于业务开发人员,bit 位这个概念既熟悉又陌生,熟悉是因为整个计算机就是建立在bit基础之上,同时任何一门语言都对 bit 位提供了支持;陌生是因为工作过程中基本没有使用过,说起具体的操作语法估计也需要好好思考一下。
不像底层研发人员,比如驱动开发、网络协议开发等,成天与 bit 混在一起。业务开发人员对于 bit 位就陌生了太多。但,在一些特殊场景,bit 位还真的是一个非常好的解决方案。
1. 业务人员眼中的bit位
在业务开发人员眼里,bit位就是特殊的 一对多 关系,可以通过一个数值来表达多个 boolean 语义!
既然大家对 bit 不太熟悉,那就请出封装利器,自己动手打造一套面向对象且更好用的工具。
2. 单一Bit位操作
对于单一 Bit 位,操作非常简单,只有:
-
设置 bit 位为 1(true)
-
设置 bit 位为 0 (false)
-
验证 bit 位 是否为 1
操作代码如下:
@Data
public class LongMaskOp{
private final long mask;
public LongMaskOp(long mask ) {
this.mask = mask;
}
public boolean isSet(long code){
return (code & this.mask) == this.mask;
}
public boolean match(long value) {
return isSet(value);
}
public long set(long value, boolean isSet){
if (isSet){
return set(value);
}else {
return unset(value);
}
}
public long set(long value){
return value | this.mask;
}
public long unset(long value){
return value & ~this.mask;
}
}
使用也非常简单,获取 LongMaskOp 之后便可以使用各种方法进行位操作,示例如下:
long value = ?;
LongMaskOp longMaskOp = ?;
value = longMaskOp.set(value);
Assertions.assertTrue(longMaskOp.isSet(value));
value = longMaskOp.unset(value);
Assertions.assertFalse(longMaskOp.isSet(value));
代码非常简单,在此不在赘述,有了 LongMaskOp 类之后,如何获取对应的实例呢?
当然,可以通过 new 关键字直接实例化对象,但每个位上的 MaskOp 是完全一样的,这就让我们想到了单例模式。
对于 Long 类型最多也就 64 个对象,我们可以一次性将其创建出来,然后通过传入的参数返回对应位置上的值,具体操作步骤如下:
-
将 LongMaskOp 构造函数设置为 private,禁止从外部创建对象
-
LongMaskOp 中定义 64 个 静态变量,每一位对于一个 LongMaskOp 实例
-
将 64 个 LongMaskOp 实例统一放在List集合,方便按位数进行查找
-
提供静态方法,根据传入的位数返回对应的 LongMaskOp 实例
核心代码如下:
@Data
public class LongMaskOp{
// 1. 私有构造函数,禁止从外部创建对象
private LongMaskOp(long mask ) {
this.mask = mask;
}
// 2. 定义 64 个静态变量
public static final LongMaskOp MASK_1 = new LongMaskOp(1L << 0);
public static final LongMaskOp MASK_2 = new LongMaskOp(1L << 1);
...... 省略部分代码
public static final LongMaskOp MASK_64 = new LongMaskOp(1L << 63);
// 3. 将 64 个变量放入 List 集合
MASK_OPS = Arrays.asList(LongMaskOp.MASK_1, LongMaskOp.MASK_2, .... 省略部分代码, LongMaskOp.MASK_64);
// 4. 提供静态方法获取对应位置的 LongMaskOp 对象
/**
* 根据 bit 位的位置,获取对应的封装实例 <br />
* Index 从 1 开始
* @param index
* @return
*/
public static LongMaskOp getByBitIndex(int index){
if (index < 1 || index > MASK_OPS.size()){
throw new IndexOutOfBoundsException();
}
return MASK_OPS.get(index - 1);
}
}
单例模式改造完成,通过以下代码可以方便的获取 Bit 操作类:
LongMaskOp bitOp1 = LongMaskOp.getByBitIndex(1);
整体结构如图所示:

3. Bit 位逻辑运算
Bit 位支持 and、or、not 等逻辑运算,我们的 LongMaskOp 也需要提供对应的支持,可以思考一下,哪种模式可以解决这个问题?
对,这就是组合模式的应用场景。要想应用组合模式,需要:
-
抽取公共接口
-
构建叶子节点
-
构建组合节点
目前,LongMaskOp 实现了真正的业务逻辑,是我们的叶子节点。我们需要抽取公共接口,然后为每个逻辑运算构建“组合节点”。
首先,抽取公共接口,接口中只有一个 match 方法,用于判断是否匹配,LongMaskOp 实现该接口并实现 match 方法:
public interface LongBitOp {
boolean match(long value);
}
@Data
public class LongMaskOp implements LongBitOp {
@Override
public boolean match(long value) {
return isSet(value);
}
}
接下来,可以构建对 And、Or、Not 的支持。核心代码如下:
public class LongBitAndOp implements LongBitOp {
private final LongBitOp[] longBitOps;
LongBitAndOp(LongBitOp... longBitOps) {
this.longBitOps = longBitOps;
}
@Override
public boolean match(long value) {
if (this.longBitOps == null || this.longBitOps.length == 0){
return true;
}
return Stream.of(longBitOps)
.allMatch(longMaskOp -> longMaskOp.match(value));
}
}
public class LongBitOrOp implements LongBitOp {
private final LongBitOp[] longBitOps;
LongBitOrOp(LongBitOp... longBitOps) {
this.longBitOps = longBitOps;
}
@Override
public boolean match(long value) {
if (this.longBitOps == null || this.longBitOps.length == 0){
return true;
}
return Stream.of(this.longBitOps)
.anyMatch(intBitOp -> intBitOp.match(value));
}
}
public class LongBitNotOp implements LongBitOp {
private final LongBitOp longBitOp;
LongBitNotOp(LongBitOp longBitOp) {
this.longBitOp = longBitOp;
}
@Override
public boolean match(long value) {
return !this.longBitOp.match(value);
}
}
逻辑非常简单:
-
LongBitAndOp 只有在所有的条件全部匹配时才返回 true
-
LongBitOrOp 只要有一个条件匹配便返回 true
-
LongBitNotOp 将条件判断结果取反并返回
万事具备,如何更好的暴露 API 呢?
首先,And、Or、Not 操作每个 LongBitOp 都应该支持,那放在 LongBitOp 接口最为合适,放在接口中所有的子类都需要实现,哪岂不是更麻烦?
Java 8 引入一个新特性:默认方法,可以为接口添加默认实现,一起瞧瞧:
public interface LongBitOp {
boolean match(long value);
// 默认方法
default LongBitOp or(LongBitOp other){
return new LongBitOrOp(this, other);
}
// 默认方法
default LongBitOp and(LongBitOp other){
return new LongBitAndOp(this, other);
}
// 默认方法
default LongBitOp not(){
return new LongBitNotOp(this);
}
}
这样每个 LongBitOp 子类都具备了 And、Or、Not 能力,新 API 使用起来也非常简单:
LongMaskOp bitOp1 = LongMaskOp.getByBitIndex(1);
LongMaskOp bitOp2 = LongMaskOp.getByBitIndex(2);
LongBitOp orBitOp = bitOp1.or(bitOp2);
LongBitOp andBitOp = bitOp1.and(bitOp2);
LongBitOp notBitOp = bitOp1.not();
4. 数据库过滤
Bit 位操作仅停留在 内存 吗?不一定哈,MySQL 的 sql 语句便提供了 bit 位操作,可以在数据库层对数据进行过滤。接下来,继续对 LongBitOp 进行扩展,使其能产生语义相同的 sql 片段,辅助我们完成数据过滤。
首先,在 LongBitOp 接口中增加生成 SQL 片段的方法:
public interface LongBitOp {
String toSqlFilter(String fieldName);
}
接下来,让 LongMaskOp 实现 toSqlFilter 方案:
@Data
public class LongMaskOp implements LongBitOp {
@Override
public String toSqlFilter(String fieldName) {
return new StringBuilder()
.append("(")
.append(fieldName)
.append(" & ")
.append(getMask())
.append(")")
.append("=")
.append(getMask())
.toString();
}
}
没什么复杂的,主要就是 字符串 拼接,下一步对LongBitAndOp、LongBitOrOp、LongBitNotOp 进行完善:
public class LongBitAndOp implements LongBitOp {
// 使用 and 连接多个语句
@Override
public String toSqlFilter(String fieldName) {
if (this.longBitOps == null || this.longBitOps.length == 0){
return "";
}
return Stream.of(longBitOps)
.map(intBitOp -> intBitOp.toSqlFilter(fieldName))
.collect(Collectors.joining(" and ","(",")"));
}
}
public class LongBitOrOp implements LongBitOp {
// 使用 or 连接多个语句
@Override
public String toSqlFilter(String fieldName) {
if (this.longBitOps == null || this.longBitOps.length == 0){
return "";
}
return Stream.of(this.longBitOps)
.map(intBitOp -> intBitOp.toSqlFilter(fieldName))
.collect(Collectors.joining(" or ","(",")"));
}
}
public class LongBitNotOp implements LongBitOp {
// 拼接 <> 语句
@Override
public String toSqlFilter(String fieldName) {
LongMaskOp longMaskOp = (LongMaskOp) longBitOp;
return new StringBuilder()
.append("(")
.append(fieldName)
.append(" & ")
.append(longMaskOp.getMask())
.append(")")
.append("<>")
.append(longMaskOp.getMask())
.toString();
}
}
整体调整完成,一起看下实际效果:
LongBitOp bitOp1 = LongMaskOp.getByBitIndex(1);
LongBitOp bitOp2 = LongMaskOp.getByBitIndex(2);
LongBitOp bitOp3 = LongMaskOp.getByBitIndex(3);
// 直接过滤
Assertions.assertEquals("(type & 1)=1", bitOp1.toSqlFilter("type"));
Assertions.assertEquals("(type & 2)=2", bitOp2.toSqlFilter("type"));
Assertions.assertEquals("(type & 4)=4", bitOp3.toSqlFilter("type"));
// not 条件过滤
Assertions.assertEquals("(type & 1)<>1", bitOp1.not().toSqlFilter("type"));
Assertions.assertEquals("(type & 2)<>2", bitOp2.not().toSqlFilter("type"));
Assertions.assertEquals("(type & 4)<>4", bitOp3.not().toSqlFilter("type"));
// and 条件过滤
Assertions.assertEquals("((type & 1)=1 and (type & 2)=2)", bitOp1.and(bitOp2).toSqlFilter("type"));
Assertions.assertEquals("((type & 2)=2 and (type & 4)=4)", bitOp2.and(bitOp3).toSqlFilter("type"));
Assertions.assertEquals("((type & 1)=1 and (type & 4)=4)", bitOp1.and(bitOp3).toSqlFilter("type"));
// or 条件过滤
Assertions.assertEquals("((type & 1)=1 or (type & 2)=2)", bitOp1.or(bitOp2).toSqlFilter("type"));
Assertions.assertEquals("((type & 2)=2 or (type & 4)=4)", bitOp2.or(bitOp3).toSqlFilter("type"));
Assertions.assertEquals("((type & 1)=1 or (type & 4)=4)", bitOp1.or(bitOp3).toSqlFilter("type"));
5. 整体设计
打完收工,至此,我们拥有了一个功能强大的 Bit 位操作工具,不仅能够在内存数据中进行运算,还可以生成 sql 片段,在数据库中完成数据过滤。
最后,一起看下整体设计:

这就是封装的艺术,不知道你能 get 多少。
6. 项目信息
项目仓库地址:https://gitee.com/litao851025/lego
项目文档地址:https://gitee.com/litao851025/lego/wikis/support/bitop
原文始发于微信公众号(geekhalo):从Bit位操作学封装
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/60317.html