讲讲我对Java位运算的理解

来源:互联网 发布:一般淘宝客佣金关闭 编辑:程序博客网 时间:2024/04/19 12:17

阅读前提:

1、懂二进制、十进制、十六进制,并知道如何转换。

2、懂Java。


内容概括:

Java位运算相关,包括三个移位、四个位运算,很简单很基础很低端。


正文:

Java中的位运算符有以下几种:

<<左移

>>右移

>>>无符号右移(又称逻辑右移,相应的>>就称为算术右移)

与(&)、非(~)、或(|)、异或(^)


以上所有的运算,都是基于二进制数发生的。比如你要手动计算8左移之后变成多少,必须先把8转换成二进制的1000才可以。

Java里写一个数字的时候,直接写就是十进制。如果写成0x开头的,就是十六进制数。比如0x10其实就是16。(八进制是0开头,不表)。似乎不能输入二进制数字,大概是因为太长了。但是可以用Integer.toBinaryString(int)方法来得到一个int值的二进制表示。

还应该知道,Java里的基本数据类型int和long是有上下限的。如果转换成二进制的话,int是32位,long是64位。学过计组的应该知道,为了区分正负数,用二进制数的第一位来当符号,0正1负。也就是说真正的数字是int后面的31位和long后面的63位(不过负数并不是简单地把正数的首位改成1而已,负数是用补码来存储的,不了解补码可以去百度一下)。查JDK文档就会发现,它们的上下限分别是-231~231-1和 -263~263-1。顺带一提short是16位,byte是8位。但是这两个类型的变量一位运算,就会自动变成Int……因为有上下限,所以如果运算得太过分的话,会发生上溢或者下溢,然后数字就会变得很奇怪。

然后看以下程序:

        int n = 3;        System.out.println(n);        System.out.println(n<<1);        System.out.println(n>>1);        System.out.println(n>>>1);                System.out.println("*****");                System.out.println(Integer.toBinaryString(n));        System.out.println(Integer.toBinaryString(n<<1));        System.out.println(Integer.toBinaryString(n>>1));        System.out.println(Integer.toBinaryString(n>>>1));                System.out.println("*****");                n=-3;        System.out.println(n);        System.out.println(n<<1);        System.out.println(n>>1);        System.out.println(n>>>1);                System.out.println("*****");                System.out.println(Integer.toBinaryString(n));        System.out.println(Integer.toBinaryString(n<<1));        System.out.println(Integer.toBinaryString(n>>1));        System.out.println(Integer.toBinaryString(n>>>1));

运行结果:

3
6
1
1
*****
11
110
1
1
*****
-3
-6
-2
2147483646
*****
11111111111111111111111111111101
11111111111111111111111111111010
11111111111111111111111111111110

1111111111111111111111111111110


通过观察结果就可以了解以下内容:

左移n位,其实就是乘以2n;右移n位,则是除以2n

左移时,右边补的是0。算术右移时,左边补的是符号位(也就是正数补0,负数补1)。逻辑右移时,左边补的是0。

左移可能会上溢。上溢时有可能会变号。负数进行逻辑右移的话,肯定是会变号的。


位运算符,把两个二进制数每一位的数值进行运算,得出结果。

与(&): 双方都为1时结果才是1,其他情况全是0. 1&1=1; 1%0=0; 0&0=0

非(~):取反用。~1=0; ~0=1

或(|):双方都为0时,结果才是0,其他情况全是1. 0&0=0; 0&1=1; 1&1=1;

异或(^):双方不同时,结果是1,双方相同时,结果是0. 1^1=1; 0^0=1; 0^1=0;

再总结一下对一个已知数进行位运算之后,它会变成什么样:

1、对某一位x进行&1,它会保持原样。&0,它会变成0.对某一位x进行|1,它会变成1。|0,它会保持原样。这四个会非常常用,可以在一些位不变的情况下,把某些位消成0或者补成1。比如,对一个数字进行&11000,只会留下11那两位保留原数字,其他位全都变成0。对一个数字进行|11000,只会留下000三位原数字,其他位都变成1。

2、对某一位x进行~,会取反,但是缺点是,~只能对整个数字进行。灵活度明显不够,但是也是不可或缺的存在。

3、异或的用法大概是用来保留一个数字所有的1位,或者所有的0位。如果^一个全位都是0的数字,结果还是原数字。如果^一个全位是1的数字,结果是原数字全位取反,效果等同于~


然后稍微联想一下这些符号都可以怎样使用,就拿比较常见的RGB颜色取法来作例子吧。

一般我们把RGB颜色存成一个6位的十六进制数,类似0xFF5600。其中FF这两位代表红色的值,56这两位代表绿色的值,00这两位代表蓝色的值。所以我们实际上是有0~255这256(16x16)个值来定义每个颜色。

我们在改颜色的时候,可能只需要改一种颜色,这样调整起来比较方便。比如我想把“绿色”的值增大4,这样颜色看起来就会单纯地“发绿”。那么要如何处理0xFF5600这个数值,让它的“绿色”增加4呢?或者想让绿色的部分颜色值为0,绿色部分颜色值为最大(0xFF)。

        int c = 0xFF5600;        System.out.println(Integer.toHexString(c));        System.out.println(Integer.toHexString((4<<8)+0xFF5600));        System.out.println(Integer.toHexString(~(0xFF<<8)&0xFF5600));        System.out.println(Integer.toHexString((0xFF<<8)|0xFF5600));

以上代码就可以办到。十六进制的两位,在二进制里相当于八位。所以<<8。其他就比较简单,不详细说了。

输出为:

ff5600
ff5a00
ff0000
ffff00


至此关于位运算的基本内容就都介绍完了,然后列一些很经典的妙用位运算实现的运算。感兴趣的话可以去搜搜找更多。

1、判断整数的奇偶

二进制数取最后一位,就可以判断奇偶。

n&1==1奇数

n&1==0偶数

2、整数的平均值
对于两个整数x,y,如果用 (x+y)/2 求平均值,会产生溢出,因为 x+y 可能会大于INT_MAX,但是我们知道它们的平均值是肯定不会溢出的,我们用如下算法:
int average(int x, int y)  //返回X,Y 的平均值
{   
    return (x&y)+((x^y)>>1);
}

这个的原理是:

把x和y的所有位拿出来,分成两部分,一部分是x和y的对应位都是1(这样的位,下面简称”全1位“),另一部分是x和y的对应位一个是1一个是0(下面简称”01位“). 对应位全是0的情况,相加还是0,所以不用计算也可以。

对于第一部分,用x&y计算后,所有的全1位都会是1,其他位则全是0.因为实际上我们要求的是 (x的全1位+y的全1位)/2 ,其实就是(某一位的1+对应位的1)/2,结果当然是1.也就是说,全1位相加之后再除以2的结果,是1.这样的话,我们直接x&y算出来的结果,其实就是全1位相加后的平均值。

对于第二部分,用x^y计算的结果,所有01位全是1,其他位全是0. 我们要求的是 (某一位的0+对应位的1)/2 。 也就是说光得到x^y是不够的,这个数还得再除以2,于是用了算术右移1位,相当于除以2.

最后把第一和第二部分一加,得到结果。

3、计算绝对值
int abs( int x )
{
int y ;
y = x >> 31 ;
return (x^y)-y ;        //or: (x+y)^y
}

y = x >> 31得到的一个全0或者全1的32位二进制数。(全0是十进制里的0,全1是十进制里的-1)

(x^y)在x为正时,相当于x,在x为负时,相当于~x。

-y,x为正时,相当于0,x为负时,相当于1

所以以上算法总结之后就是:正数的话直接是原值,负数的话,会取反再+1。会发现其实这是根据原码求补码的过程……