网站首页 文章专栏 高性能的位运算,以及在实际项目中的运用
高性能的位运算,以及在实际项目中的运用

一. 实际项目常见场景

比如就拿我自己博客网站来说,我的每一篇文章都有,是否顶置,是否公开,是否原创,是否热文,是否推荐等属性,那么对应的我就要在数据库中新建5个字端,分别用0和1表示是和否,其实这是很浪费的,每个字段就两个状态,以后如果还要增加字段的话,那么还要加数据库字段。

那么可能你们还见过用一个字段,0表示xxx,1表示xxx,2表示xxx,3表示xxx等,用这种方式可以吗,有的场景可以,有的就不太行,以博客文章为例,文章可能同时有多种属性,既是顶置文章,又是原创文章,这种方案就不行了,因为不能同时表示多种状态。

今天讲的位运算,就是可以解决这个问题,用一个字段就能同时表示多种状态。

二. 基本前置知识

左移,右移:

左移(<<): 符号位不变,低位补0。如:2<<2结果为8。

右移(>>): 低位溢出,符号位不变,并用符号位补溢出的高位。如:-6>>2结果为-2。


按位与(&)

按位与的运算规则

操作数1

0

0

1

1

操作数2

0

1

0

1

按位与

0

0

0

1

规则总结:只有两个操作数对应位同为1时,结果为1,其余全为0. (或者是只要有一个操作数为0,结果就为0)


按位或( | )

按位或运算规则:

操作数1

0

0

1

1

操作数2

0

1

0

1

按位或

0

1

1

1

规则总结:只有两个操作数对应位同为0时,结果为0,其余全为1.(或者是只要有一个操作数为1,结果就为1)。


按位异或(^)

按位异或运算规则:

操作数1

0

0

1

1

操作数2

0

1

0

1

按位异或

0

1

1

0

规则总结:相异为1,相同为0

三. 如何用位运算用一个int值表示多种状态

一个int4个字节,32位,如果让每一个二进制位拥有特殊含义,那么用一个值就能实现多种状态的压缩,如下图所示,实际上这种方式运用的很广,就是位图bitmap的思想,redis的bitmap结构,布隆过滤器都是用了类似的设计。


image.png

下面进行实战:

首先我们先定义每个位置上的状态表示什么

image.png

先定义好:

1 :第一位表示是否公开

1 << 1 = 2 :第二位表示是否顶置

1 << 2 = 4 : 第三位表示是否原创

定义的化需要用2的整数幂表示,因为整数幂只有自己位为1,其他位为0,方便操作。


那么我们如果想用一个值同时表示上面三个状态该怎么处理呢?

image.png

将定义的3个状态值进行或处理,或操作就是将1进行汇聚,如上图,第一位,第二位,第三位的1进行或操作后,变成的值三位都是1,那么1|2|4 = 7,这个7就能表示既公开,也顶置,也原创。


如果我从数据库读取的值为7,那么我怎么知道他包含哪几个状态呢?

image.png

如果我们想验证7这个状态是否顶置,该怎么办,就可以将7与我们想要验证的属性(也就是定义的1 << 1 = 2)进行&操作,判断结果是否为想验证的值,如果等于那么就表示顶置,如果不等于,那么就是不顶置,原理见上图。不明白的话,可以多缕缕。


现在7已经表示,公开,顶置,原创了,如果我想要修改去除某个属性怎么办呢?比如想去除顶置属性。

image.png

很简单,将7与想移除的属性,进行^操作,异或是相异为1,相同为0,其实就是把目标位的1变成0,也就移除掉了,同时不影响其他位。

四. 代码实操

实体类Article:

@ToString
@EqualsAndHashCode
@Builder
public class Article {

    @Getter
    @Setter
    private Integer id;

    @Getter
    @Setter
    private String articleName;

    @Getter
    @Setter
    private String articleDesc;

    @Getter
    @Setter
    private LocalDateTime createTime;

    @Getter
    @Setter
    private Integer status;

    private final boolean articlePublic;
    private final boolean articleUp;
    private final boolean articleOriginal;
    private final boolean articleHot;
    private final boolean articleRecommend;

    public boolean isArticlePublic() {
        return ArticleStatus.hasStatus(this.status,ArticleStatus.ARTICLE_PUBLIC);
    }

    public boolean isArticleUp() {
        return ArticleStatus.hasStatus(this.status,ArticleStatus.ARTICLE_UP);
    }

    public boolean isArticleOriginal() {
        return ArticleStatus.hasStatus(this.status,ArticleStatus.ARTICLE_ORIGINAL);
    }

    public boolean isArticleHot() {
        return ArticleStatus.hasStatus(this.status,ArticleStatus.ARTICLE_HOT);
    }

    public boolean isArticleRecommend() {
        return ArticleStatus.hasStatus(this.status,ArticleStatus.ARTICLE_RECOMMEND);
    }
}

使用了status表示所有状态,数据库也只存储这个字段,下面的articlePublic, articleUp,articleOriginal 等字段不进入数据库存储,写在这里是为了使用方便,且定义为final,没写set方法,只在get的时候,通过ArticleStatus类将 status 进行转换。


定义枚举ArticleStatus,并写了一些添加状态,判断,移除状态的方法。

@Getter
public enum ArticleStatus {

    ARTICLE_PUBLIC(1,"公开文章"),
    ARTICLE_UP(1 << 1,"顶置文章"),
    ARTICLE_ORIGINAL(1 << 2,"原创文章"),
    ARTICLE_HOT(1 << 3,"hot文章"),
    ARTICLE_RECOMMEND(1 << 4,"推荐文章"),
    ;

    private int code;

    private String message;


    ArticleStatus(int code, String message) {
        this.code = code;
        this.message = message;
    }

    /**
     * 检查是否存在某个属性
     */
    public static Boolean hasStatus(Integer status, ArticleStatus articleStatus){
        return (articleStatus.code & status) == articleStatus.code;
    }

    /**
     * 在原来的基础上添加某种属性
     */
    public static int addStatus(Integer status, ArticleStatus articleStatus){
        if(hasStatus(status, articleStatus)){
            return status;
        }
        return (status | articleStatus.code);
    }

    /**
     * 移除某种属性
     */
    public static int removeStatus(Integer status, ArticleStatus articleStatus) {
        if(!hasStatus(status, articleStatus)){
            return status;
        }
        return status ^ articleStatus.code;
    }

    /**
     * 获取当前值包含的所有状态信息
     */
    public static List binaryToList(int status) {
        return Arrays.stream(values()).filter(articleStatus -> hasStatus(status, articleStatus))
                .map(ArticleStatus::getMessage).collect(Collectors.toList());
    }
}

测试:

public static void main(String[] args) {

    /**
     * 初始化状态为公开文章
     */
    int status = ArticleStatus.ARTICLE_PUBLIC.getCode();

    // 增加顶置属性,原创属性,热文属性
    status = ArticleStatus.addStatus(status,ArticleStatus.ARTICLE_UP);
    status = ArticleStatus.addStatus(status,ArticleStatus.ARTICLE_ORIGINAL);
    status = ArticleStatus.addStatus(status,ArticleStatus.ARTICLE_HOT);

    Article article = Article.builder()
            .id(1)
            .articleName("测试文章")
            .articleDesc("描述信息")
            .createTime(LocalDateTime.now())
            .status(status)
            .build();

    System.out.println("入库文章信息:" + article);
    System.out.println("入库文章包含的属性:" + ArticleStatus.binaryToList(article.getStatus()));

    // 移除文章的公开属性
    status = ArticleStatus.removeStatus(status,ArticleStatus.ARTICLE_PUBLIC);
    article.setStatus(status);

    System.out.println("更新的文章信息:" + article);
    System.out.println("更新的文章包含的属性:" + ArticleStatus.binaryToList(article.getStatus()));

}

输出结果:

入库文章信息:Article(id=1, articleName=测试文章, articleDesc=描述信息, createTime=2021-04-26T15:10:10.643, status=15, articlePublic=true, articleUp=true, articleOriginal=true, articleHot=true, articleRecommend=false)
入库文章包含的属性:[公开文章, 顶置文章, 原创文章, hot文章]

更新的文章信息:Article(id=1, articleName=测试文章, articleDesc=描述信息, createTime=2021-04-26T15:10:10.643, status=14, articlePublic=false, articleUp=true, articleOriginal=true, articleHot=true, articleRecommend=false)
更新的文章包含的属性:[顶置文章, 原创文章, hot文章]

可以看到,我们只用了一个字段,就很方便的管理了5个字段属性,通过工具类进行状态添加,状态移除,状态判断等,在表数据量很大时,这种方式就节省了很多空间。


五. 总结

本文通过原理,实际代码等讲解了位运算在实际项目中的应用,这只是其中一个场景,在一些流程状态中,会有很多状态,且这些状态有先后顺序的,就可以用这种方式记录已执行的状态;

再比如,权限系统中,可以用多个int表示多个权限组(可以理解为角色),每个组有自己权限,每个人可以属于多个组,等等,也可以用到这种思想;

或者多个业务组存在多个业务,也是上面类似的思路。总之,就是位图的思想,状态压缩,一个值可以表示多种状态,而且如果状态超过32,可以用long表示。



版权声明:本文由星尘阁原创出品,转载请注明出处!

本文链接:http://www.52xingchen.cn/detail/87




赞助本站,网站的发展离不开你们的支持!
来说两句吧
大侠留个名吧,或者可以使用QQ登录。
: 您已登陆!可以继续留言。
最新评论