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

2018-12-01  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读