美文网首页
位运算还能这么用,高阶玩法

位运算还能这么用,高阶玩法

作者: 搬运工来架构 | 来源:发表于2018-12-01 22:26 被阅读30次

    位运算,基本上没有一个理工科生不知道的基础知识,即使你在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

    protected void doWrite(ChannelOutboundBuffer in) throws Exception {

    final SelectionKey key = selectionKey();

    final int interestOps = key.interestOps();

    for (;;) {

    Object msg = in.current();

    if (msg == null) {

    // Wrote all messages.

    if ((interestOps & SelectionKey.OP_WRITE) != 0) {

    key.interestOps(interestOps & ~SelectionKey.OP_WRITE);

    }

    break;

    }

    try {

    boolean done = false;

    for (int i = config().getWriteSpinCount() - 1; i >= 0; i--) {

    if (doWriteMessage(msg, in)) {

    done = true;

    break;

    }

    }

    if (done) {

    in.remove();

    } else {

    // Did not write all messages.

    if ((interestOps & SelectionKey.OP_WRITE) == 0) {

    key.interestOps(interestOps | SelectionKey.OP_WRITE);

    }

    break;

    }

    } catch (Exception e) {

    if (continueOnWriteError()) {

    in.remove(e);

    } else {

    throw e;

    }

    }

    }

    }

    我们重点看:

    // Wrote all messages.

    if ((interestOps & SelectionKey.OP_WRITE) != 0) {

    key.interestOps(interestOps & ~SelectionKey.OP_WRITE);

    }

    // Did not write all messages.

    if ((interestOps & SelectionKey.OP_WRITE) == 0) {

    key.interestOps(interestOps | SelectionKey.OP_WRITE);

    }

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

    public static final int OP_READ = 1 << 0;

    public static final int OP_WRITE = 1 << 2;

    public static final int OP_CONNECT = 1 << 3;

    public static final int OP_ACCEPT = 1 << 4;

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

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

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

    int status = 0;

    System.out.println(padding(Integer.toBinaryString(status), 8));

    // add status

    status |= 1;

    System.out.println(status + "->" + padding(Integer.toBinaryString(status), 8));

    status |= 2;

    System.out.println(status + "->" + padding(Integer.toBinaryString(status), 8));

    status |= 4;

    System.out.println(status + "->" + padding(Integer.toBinaryString(status), 8));

    // exist status

    boolean b = (status & 1) != 0;

    System.out.println(status + "->" + b);

    b = (status & 2) != 0;

    System.out.println(status + "->" + b);

    b = (status & 4) != 0;

    System.out.println(status + "->" + b);

    b = (status & 8) != 0;

    System.out.println(status + "->" + b);

    // get status(remove status)

    status &= ~4;

    System.out.println(status + "->" + padding(Integer.toBinaryString(status), 8));

    status &= ~1;

    System.out.println(status + "->" + padding(Integer.toBinaryString(status), 8));

    status &= ~2;

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

    若有误,欢迎指点一二。

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

    欢迎关注w x公 众 号【搬运工来架构】

    参考:

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

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

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

    相关文章

      网友评论

          本文标题:位运算还能这么用,高阶玩法

          本文链接:https://www.haomeiwen.com/subject/juhdcqtx.html