Java笔记:位运算之异或交换数值原理

转载:Java笔记:位运算之异或交换数值原理

在学习任何语言时,都会提及到一种容易被大家忽视的运算方法–>位运算
在java中,由于JVM机制的存在,使得位运算存在感更加低,并且,为了程序的可读性,也有程序员不愿意使用位运算进行操作。
但是位运算的优秀性能,作为一个程序员是不得不学习,不管是否会用到,都应学习学习其中的原理及思想,这给我们带来的影响是潜移默化的。
基本位运算符 : & | ^ ~ << >>
这里咱们就来说说其中的异或 ^
异或,顾名思义就是相异为真
例如:

public class Demo8 {
    public static void main(String[] args){
        int x=0b11001;  // 25
        int y=0b10101;  // 21
        System.out.println(x^y);    
        // print 12

        // 11001^10101=01100
    }
}

相同位上存在1和0即为1,其他均为0,也就是异或中出现的1必定是两个二进制数中有一个的对应为上必定为1且另一个数对应位必定为0,即对应位上必定不同,这也就引出了在异或中非常经典的用法,即交换数值
首先,在咱们传统的想法上,交换两数数值如下:

public class Demo8 {
    public static void main(String[] args){
        int x=1;
        int y=2;
        int z;
        z=x;
        x=y;
        y=z;
        // y=1,x=2
    }
}

也就是利用第三个中间变量达到简单直接的交换效果,也是泛用广的一种方法。
聪明一点的同学也可能会发现不需要中间变量也可以实现两数数值交换效果,
如下:

public class Demo8 {
    public static void main(String[] args){
        int x=1;
        int y=2;
        x=x+y;
        y=x-y;
        x=x-y;
        // y=1,x=2
    }
}

而异或操作,类似与上面的算法,但是原理也正如我上面讲的,提出两数不同的部分,先看例子:

public static void main(String[] args){
        int x=11;   //  1011
        int y=7;    //  0111
        x=x^y;
            //  x=1011^0111=1100  1100就是两个二进制数的不同部分
        y=x^y;
            //  y=1100^0111=1011
        x=x^y;
            //  x=1100^1011=0111
        System.out.println(x+" "+y);
        // print 7 11
 }

相信大家之前一定也了解过这种算法,但是其原理或许略知一二

原理很简单,首先,我们将11和7的二进制数1011和0111进行异或,取出他们的不同部分,即1100(下面就称其为不同部分)。拿到不同部分了,然后怎么办呢?
不同部分为1100,就是11为不同的位,00位相同的位,也就是说00位上是两个数都为1或0的位,而11位是两个数只有一个为1的部分,那么,如果我那1100和其中一个数再做异或会怎样呢?为什么会交换数呢?

首先,拿到不同部分1100,既然知道11位上必定有一个数为1,有一个数为0,那么,拿去异或,为1的和1异或就为0,就成了另一个数上对应位的值(因为这个数为1,另一个数必定是0),如果1是和0异或,那么相对的就为1了,同样的道理。而至于0值,拿去异或,被异或的数相对位上原本是什么数就该是什么数,不会变(而且这个0位上的值是两个数相同的值)
同样拿上面的11和7举例,x=1011和y=0111,不同部分为1100,拿去和x异或,因为不同点前两位为0,所以x的第一位和第二位保持原样不变,该是0就是0,该是1就是1,而且y上第一位和第二位必定和x的前两位相同,因为只有相同异或出来的结果才为0。而第三位不同部分为1,和x异或后,因为x第三位为0,所以不同,x第三位变为1,但是想想,既然不同部分第三位为1,那么x的第三位和y的第三位必定不同,既然x为0,那么y的第三位必定为1,所以x的第三位顺理成章的就变成的y的第三位,第四位同理,不同部分为1,而x第四位为1,所以异或后必定为0,而既然不同部分为1,那么x第四位和y第四位必定不同,既然x第四位为1,那么y的第四位必定为0,所以异或后x第四位就变成了和y的第四位相同,又因为不同部分的00位必定是x和y相同的,而异或后x和y的不同位又因为异或变成了和y相同的值,也就是说,此时x和不同部分异或后,二进制上变成了y的值了,如果在用y和不同部分异或,同样的道理,就变成了x的值了。

至于为什么没用到第三个数,道理和之前的第二个例子一样,临时用x做了一个存放不同部分的容器,然后y=x^y, 因为此时x为不同部分,和y异或后,如上所述,就变成了x的二进制了,如果再让x(不同部分)与此时的y(此时已经变成x的值了)异或,即x=x^y,因为y此时已经为x的值了,所以不同部分和x异或后,自然会得到y的二进制,即y的值,也就是说此时x为y的值。
综上所述,就完成了我上面那个例子的整个过程。
如果有些朋友还不能理解,可以拿笔在纸上简单画一画,很快就能理解了。
位运算的优势无非是在于直接在机器层面对数进行操作了,因为计算机的数据存储均为二进制,所以理论上是能操作所有数值的,当然,具体还得根据情况而定,不然也会得到出人意料的结果。
至于位运算的取舍,有人不愿意为了提升这百分之几的性能而混淆代码的可读性,有人又认为性能才是程序之根本,尤其是在进行嵌入式等底层开发上,所以这就是仁者见仁智者见智了。


上面一共用到了3中方法来进行交换操作。
第一种就是搞一个中间变量,这个的原理大家都清楚,我举个形象一点的例子,换货,你们家有一个沙发,才买两天 就坏了,这时候上家就带着新沙发来换旧的。

  • 商家把新沙发从车上卸载下来放到地面上
  • 把你们家沙发抬出来放到车上
  • 再把地面上的沙发抬到你家

这三个步骤中,地面就是那个第三方存寄处,你们家通过这个地面完成了换货。

第二种就是两数相加的方式,这个原理也好解释。我有一个苹果,你有一个香蕉,我们因为不信任对方但又想交换物品,所以就找了 一个坛子,这个坛子的口一次只能取出一样东西,我们把苹果和香蕉都放进去,你先取出你想要的的苹果,然后剩下的(总共 减去 你取走的)就是我想要的香蕉。

第三种上面讲的比较详细,所以我也不细说了,就说一下看问题的思想,因为二进制是由0和1构成的,不像我们常用的10进制;而我们看东西都是习惯以一个整体去看待,而不是零散的看待,例如我们看到一头猪,那就是一头猪,而不是猪头、猪脑、猪尾巴、猪毛等等的组合体。而这里的交换就是这样的组合体,他不是一次性交换的,他是零散的交换的,是一个一个的“异”交换的,又因为非0即1,所以不存在别的可能性。

举个不恰当的例子:我有一个南孚手机,你有一个南孚手电,我们要交换,假设我们就分别将他们都拆分成两部分,电池和其他,我们先比对,电池是一样的,其他的不一样,然后你拿着你的南孚手电来对比我们的比对结果:发现电池是一样的,继续比较;然后其他的不一样,就更换不一样的地方,然后我来比较你拿到的东西和我们的比对结果,就拿到的其他不一样的东西。相同的就不管了了,这样我们仅仅通过更换其他的东西而没有更换相同的电池就完成了整个物品的更换。


  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值