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