导航菜单
路很长,又很短
博主信息
昵   称:Cocodroid ->关于我
Q     Q:2531075716
博文数:305
阅读量:707450
访问量:66811
至今:
×
云标签 标签球>>
云标签 - Su的技术博客
博文->>首页 博主的更多博文>>
位运算还能这么用,高阶玩法
Tags : 位运算,框架,组件发表时间: 2018-12-01 21:36:14
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。
比如: 转自:Su的技术博客  原文地址:

    位运算,基本上没有一个理工科生不知道的基础知识,即使你在IT技术行业,基本对其了如指掌,但是,你还知道它还能怎么用吗?有没有更加高阶的玩法?

    位运算主要还是更加接近计算机底层的0,1计算,这些我们入门学习的时候基本上就懂了,所以这里就不进行讲述了。

    刚学习位运算的时候,一切都是那么简单,不就是位的与、或、异或、非、无符号右移、左移、右移等操作,这些对于计算机来说都是最简单最快速的操作了,但是如果我们人类要进行位运算却不是那么容易了,一般简单的还能算下,稍微复杂点的还是得需要笔纸或其它介质来辅助我们进行计算。所以,有时我们的脑袋可能不会很快的进行转换,但是基本原理懂了之后,我们就能得心应手了。

    位运算的进一步玩法,你应该也了解过:

    ①计算一个数是不是2的次幂:x && !(x & (x - 1));

    ②计算两个数的最大最小值:x ^ ((x ^ y) & -(x < y))

    ③交换两个数:x ^= y; y ^= x; x ^= y; 等等。

  想必你对上面举例的几种算法都是了如指掌或有所耳闻。其优点想必你也清楚了!接下来,我们会讲讲其它更加高阶的用法。请继续往下看吧(重点来了)。

    这里主要是使用到的位运算符:与&、或|、异或^。所以你要对其原理非常清楚,这里给下简洁的记法:

    ①与&:全true,则true;

    ②或|:一true,则true;

    ③异或^:不同,则true。

(其它情况则为false。)



    ...(这里是一条分割线)


    好了,废话不多说了,直接根据现实实例来说起吧。这里以Netty部分源码为例(zk源码同样也有类似的)

    

  @Override
 2    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
 3        final SelectionKey key = selectionKey();
 4        final int interestOps = key.interestOps();
 5
 6        for (;;) {
 7            Object msg = in.current();
 8            if (msg == null) {
 9                // Wrote all messages.
10                if ((interestOps & SelectionKey.OP_WRITE) != 0) {
11                    key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
12                }
13                break;
14            }
15            try {
16                boolean done = false;
17                for (int i = config().getWriteSpinCount() - 1; i >= 0; i--) {
18                    if (doWriteMessage(msg, in)) {
19                        done = true;
20                        break;
21                    }
22                }
23
24                if (done) {
25                    in.remove();
26                } else {
27                    // Did not write all messages.
28                    if ((interestOps & SelectionKey.OP_WRITE) == 0) {
29                        key.interestOps(interestOps | SelectionKey.OP_WRITE);
30                    }
31                    break;
32                }
33            } catch (Exception e) {
34                if (continueOnWriteError()) {
35                    in.remove(e);
36                } else {
37                    throw e;
38                }
39            }
40        }
41    }

我们重点看:

 // Wrote all messages.
2                if ((interestOps & SelectionKey.OP_WRITE) != 0) {
3                    key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
4                }
5
6                // Did not write all messages.
7                if ((interestOps & SelectionKey.OP_WRITE) == 0) {
8                   key.interestOps(interestOps | SelectionKey.OP_WRITE);
9               }

继续看SelectionKey定义的常量这个类:

public static final int OP_READ = 1 << 0;
2
3    public static final int OP_WRITE = 1 << 2;
4
5    public static final int OP_CONNECT = 1 << 3;
6
7    public static final int OP_ACCEPT = 1 << 4;

看到这样的代码,可能你会有一脸懵逼吧!这是啥意思,这逻辑是干嘛用的?

其实这里主要就是用到了位运算的高阶用法,虽然说位运算比较简单,但是其灵活魔术变法你不一定能一下子就懂其用意。

由源码我们知道OP_READ=0,OP_WRITE=4,OP_CONNECT=8,OP_ACCEPT=16。但是上面的&、|等是什么操作?我们这边做下实验吧:

  int status = 0;
 2        System.out.println(padding(Integer.toBinaryString(status), 8));
 3        // add status
 4        status |= 1;
 5        System.out.println(status + "->" + padding(Integer.toBinaryString(status), 8));
 6        status |= 2;
 7        System.out.println(status + "->" + padding(Integer.toBinaryString(status), 8));
 8        status |= 4;
 9        System.out.println(status + "->" + padding(Integer.toBinaryString(status), 8));
10
11        // exist status
12        boolean b = (status & 1) != 0;
13        System.out.println(status + "->" + b);
14        b = (status & 2) != 0;
15        System.out.println(status + "->" + b);
16        b = (status & 4) != 0;
17        System.out.println(status + "->" + b);
18        b = (status & 8) != 0;
19        System.out.println(status + "->" + b);
20
21        // remove status
22        status &= ~4;
23        System.out.println(status + "->" + padding(Integer.toBinaryString(status), 8));
24        status &= ~1;
25        System.out.println(status + "->" + padding(Integer.toBinaryString(status), 8));
26        status &= ~2;
27        System.out.println(status + "->" + padding(Integer.toBinaryString(status), 8));

结果:

0->00000000
1->00000001
3->00000011
7->00000111

7->true
7->true
7->true
7->false

3->00000011
2->00000010
0->00000000

由此,我们得出结论:

    或|操作是作为一种加操作,使用位运算将状态进行相“加”;

    与&操作是作为一种判断是否存在的操作;

    而&(-)是作为一种移除操作。(其实%(-)跟非^操作一样)


这里有一个问题,就是我定义状态标识不是2的次幂的话,能否进行同样的操作?其实是不行的,你也可以自己验证下,一旦不是2的次幂,可能会存在抹除或覆盖其它状态,因为我们是使用二进制的位作为标识我们的状态,也就是一个状态占用一个位,即2的次幂。可以从JDK NIO源码SelectionKey看出OP_WRITE进行了左移2位。


所以,回过头来看,Netty里面的源码是否就能知其意了呢?假如,不使用这种方式,你能用其它方式代替吗?当然可以,你可能觉得可以用枚举作为操作状态,进行状态的“增删查”操作。但是那样的话使用起来并不是很优雅也不灵活,而且针对性能来说,位运算绝对是计算机底层喜欢的东西,快速!


这里说下在业务场景下,有时需要存储一些状态字段,比如:订单的状态(未支付、已支付、支付成功、支付失败等)或其它业务状态。这时能否使用位运算做为我们的状态操作呢?其实这里应该不太合适,特别是在可读性上,在接手业务代码逻辑的人来说(未了解高阶位运算的前提下,如果其有看过我这篇文章,可能就不会啦^_^),这会让其很困惑。所以在业务维护上不是很建议使用这种方式。


但是在框架的开发上,特别是一些中间件或者公共组件上,可以使用这种方式灵活的使用,也能达到代码的简洁。比如:JDK NIO、Netty、Zookeeper等优秀的框架。如果你有看过其源码,对这些就不觉得奇怪了。


高阶位运算玩法总结:

    1)或|操作能做加法; &(-)或者非^能做减法;  与&操作能判断是否存在操作。

    2)定义状态位(类比)标识,一定要2的次幂,因为你操作的是二进制。

    3)业务使用位运算不提倡,可读性方面可能比较差;框架级或组件级上推荐使用高阶位运算操作。 


     

     若有误,欢迎指点一二。


    到此您有什么疑惑或者看法,若有误欢迎留言讨论,一起探究呗。


参考:

    https://www.jianshu.com/p/e2ea8bef8b56

    https://www.cnblogs.com/heluo/p/3422357.html

    https://blog.csdn.net/Airsaid/article/details/78862108


打赏
打赏
关注公众号
公众号
类别:Java| 阅读(235)| 赞 (0)
评论
暂无评论!
发表评论
昵  称:

验证码:

内  容:

    同时赞一个 赞