从Bit位操作学封装

对于业务开发人员,bit 位这个概念既熟悉又陌生,熟悉是因为整个计算机就是建立在bit基础之上,同时任何一门语言都对 bit 位提供了支持;陌生是因为工作过程中基本没有使用过,说起具体的操作语法估计也需要好好思考一下。

不像底层研发人员,比如驱动开发、网络协议开发等,成天与 bit 混在一起。业务开发人员对于 bit 位就陌生了太多。但,在一些特殊场景,bit 位还真的是一个非常好的解决方案。

1. 业务人员眼中的bit位

在业务开发人员眼里,bit位就是特殊的 一对多 关系,可以通过一个数值来表达多个 boolean 语义!

既然大家对 bit 不太熟悉,那就请出封装利器,自己动手打造一套面向对象且更好用的工具。

2. 单一Bit位操作

对于单一 Bit 位,操作非常简单,只有:

  1. 设置 bit 位为 1(true)

  2. 设置 bit 位为 0 (false)

  3. 验证 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 个对象,我们可以一次性将其创建出来,然后通过传入的参数返回对应位置上的值,具体操作步骤如下:

  1. 将 LongMaskOp 构造函数设置为 private,禁止从外部创建对象

  2. LongMaskOp 中定义 64 个 静态变量,每一位对于一个 LongMaskOp 实例

  3. 将 64 个 LongMaskOp 实例统一放在List集合,方便按位数进行查找

  4. 提供静态方法,根据传入的位数返回对应的 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);

整体结构如图所示:

从Bit位操作学封装
image

3. Bit 位逻辑运算

Bit 位支持 and、or、not 等逻辑运算,我们的 LongMaskOp 也需要提供对应的支持,可以思考一下,哪种模式可以解决这个问题?

对,这就是组合模式的应用场景。要想应用组合模式,需要:

  1. 抽取公共接口

  2. 构建叶子节点

  3. 构建组合节点

目前,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);
    }
}

逻辑非常简单:

  1. LongBitAndOp 只有在所有的条件全部匹配时才返回 true

  2. LongBitOrOp 只要有一个条件匹配便返回 true

  3. 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 片段,在数据库中完成数据过滤。

最后,一起看下整体设计:

从Bit位操作学封装
image

这就是封装的艺术,不知道你能 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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!