java位运算基本原理与实际运用

在读jdk源码时,经常会遇到形如这样的代码:

public static int numberOfLeadingZeros(int i) {
        // HD, Figure 5-6
        if (i == 0)
            return 32;
        int n = 1;
        if (i >>> 16 == 0) { n += 16; i <<= 16; }
        if (i >>> 24 == 0) { n +=  8; i <<=  8; }
        if (i >>> 28 == 0) { n +=  4; i <<=  4; }
        if (i >>> 30 == 0) { n +=  2; i <<=  2; }
        n -= i >>> 31;
        return n;
    }
或者是这样:

if ((s & (s-1)) != 0)

       由于上学时候对位操作认识不深,工作后运用也偏少,逐也渐渐淡忘,以致看到这般代码只能忽略而过,今天调试一个使用fork/join模型开发的工具时,再次遇到位运算,于是就上网找了很多资料,将位运算的基本原理和常见的应用场景整理下来。

一、关于二进制原码反码补码的基础知识

       介绍位操作之前,先介绍原码、反码、补码相关知识。我们都知道,计算机底层运算都是以二进制的方式实现的,而我们平时生活计数都是用十进制,相应我们在使用高级语言编程中也都是使用十进制,那么我们理所当然认为,编译器把十进制转位二进制计算不就可以了吗,我们也这样假设。

        我们都知道,java中int占4个字符,占4*8=32个字节,由于整数有正负则第一个字节存符号位,那么最大值就是2的31次方-1,那么二进制相加很简单:

1+1=2  二进制为: 0001(1)+0001(1)=0010(2)  (为了方便阅读,我们只用4个字节,相当于byte)

那么问题来了,因为计算机只有加法操作,没有减法操作,所以减法操作是要转化为加法操作的,那么我们再看下面的代码:

1-1=1+(-1)=0  二进制:0001(1)-0001(1)=0001(1)+1001(-1)=1010(-2) 很显然是不对的。那么就需要制定一种规则,对需要参与运算的变量应用这种规则,可以实现对减法转为加法计算,结果为我们期望的值:假设规则为fit(),则需要满足如下条件:

fit(1)+fit(1)=fit(2)、fit(1)-fit(1)=fit(1)+fit(-1)=fit(0) 转为二进制则为:fit(0001)+fit(0001)=fit(0010)、fit(0001)-fit(0001)=fit(0001)+fit(1001)=fit(0000) 

经过这帮人的琢磨,于是他们定了这么一个规则:【计算补码:负数的补码就是对反码加一,而正数不变,正数的原码反码补码是一样的。】

在补码中用(-128)代替了(-0),所以补码的表示范围为:(-128~0~127)共256个。

补码的计算很简单,在反码的基础上加一,比如1001求反码为1110(符号位不变,其他都取反),加一得1111。如此1-1的运算变为这样:

0001-10001=0001(正数补码与原码一样)+1111(负数补码)= 0000这就对了。

其实我们不难发现,负数的原码即为:正数的原码取反,这就是这就是补码的作用,这么一来,负数在计算机中的表示法就成了补码了。


二、位移运算

(1) <<     左移   
(2) >>     右移
(3) >>>    无符号右移

注意:位移操作只针对整型数据有效,包括int,byte,short,char,long,处int外,其他四种类型JVM会先把它们转换成int型再进行操作。

m<<n的含义:把整数m表示的二进制数左移n位,高位移出n位都舍弃,低位补0.  (此时将会出现正数变成负数的形式)
实例(为了便于阅读,我们只用8个字节):
 1)、 m<<n的含义:把整数m表示的二进制数左移n位,高位移出n位都舍弃,低位补0.  (此时将会出现正数变成负数的形式)

3<<2剖析:
  3 的二进制位00000011,左移两位后得到00001100,即为12. (12=3*2^2)
  注意:左移可能使整数变为负数,即如果左移后最高位为1

2)、m>>n 的含义:把整数m表示的二进制数右移n位,m为正数,高位全部补0;m为负数,高位全部补1.
实例: 
  3>>2剖析:
  3二进制形式: 00000011,右移2位得到00000000,即为0.
  -3>>2剖析:
  -3二进制形式: 11111101,右移2位,得到11111111,即为-1.(要注意了,这里看到的可是补码哈。)

以上:每个整数表示的二进制都是32位的,如果右移32位和右移0位的效果是一样的。依次类推,右移32的倍数位都一样。
  

3)、 m>>>n:整数m表示的二进制右移n位,不论正负数,高位都补零。
实例: 
  3>>>2剖析:
  3二进制形式: 00000011,高位补零右移2位,得到00000000,即为0.
  -3>>>2剖析:
  -3二进制形式: 11111101,右移,得到00111111
 
【注】:对于以上三种操作,如果n为负数:这时JVM会先让n对32取模,变成一个绝对值小于32的负数,然后再加上32,直到 n 变成一个正数。


三、按位操作

java中的位操作,包括四个操作符:
(1)~(按位非)
(2)|(按位或)
(3)&(按位与)
(4)^(按位异或)
编码中对十进制整型变量进行按位操作后,先对整型的二进制形式进行操作,然后再 转换为整数,具体操作如下。

1).~(按位非):对该整数的二进制形式逐位取反。
    ~4:(一元操作符)
     4的二进制形式为:00000100,逐位取反后得到:11111011,即为-5.
2).|(按位或):对两个整数的二进制形式逐位进行逻辑或运算,原理为:1|0=1,0|0=0,1|1=1,0|1=1
等。
    4|-5:
     4的二进制形式为:00000100,
    -5的二进制形式为:11111011,
  逐位进行逻辑或运算:11111111,即得到-1.
3).&(按位与):对两个整数的二进制形式逐位进行逻辑与运算,原理:1|0=0,0|0=0,1&1=1;0&1=0等。  
   4&-5:
     4的二进制形式为:00000100,
    -5的二进制形式为:11111011,
  逐位进行逻辑与运算:00000000,即得到0.  


4).^(按位异或):【解义】对两个整数的二进制形式逐位进行逻辑异或运算,原理:1^1=0,1^0=1,0^1=1,0^0=0.
   4^-5:
     4的二进制形式为:00000100,
    -5的二进制形式为:11111011,
逐位进行逻辑异或运算:11111111,即得到-1.


四、实际运用

1、m<<n即在数字没有溢出的前提下,对于正数和负数,左移n位都相当于m乘以2的n次方,  m>>n即相当于m除以2的n次方,得到的为整数时,即为结果。如果结果为小数,此时会出现两种情况:(1)如果m为正数,得到的商会无条件的舍弃小数位;(2)如果m为负数,舍弃小数部分,然后把整数部分加+1得到位移后的值。 比如取半数就可以用n>>1


2、按位异或可以比较两个数字是否相等,它利用1^1=0,0^0=0的原理。  20^20==0

3、 判断int型变量a是奇数还是偶数    
     a&1  = 0 偶数 
     a&1 =  1 奇数 
4、 求平均值,比如有两个int类型变量x、y,首先要求x+y的和,再除以2,但是有可能x+y的结果会超过int的最大表示范围,所以位运算就派上用场啦。
      (x&y)+((x^y)>>1); 
5、  对于一个大于0的整数,判断它是不是2的几次方
    ((x&(x-1))==0)&&(x!=0); 
6、 比如有两个int类型变量x、y,要求两者数字交换,位运算的实现方法:性能绝对高效
    x ^= y; 
    y ^= x; 
    x ^= y; 

7、 取模运算,采用位运算实现:
     a % (2^n) 等价于 a & (2^n - 1) 

8、 a % 2 等价于 a & 1 

9、 在权限的管理
public class NewPermission {
  // 是否允许查询,二进制第1位,0表示否,1表示是
  public static final int ALLOW_SELECT = 1 << 0; // 0001
  
  // 是否允许新增,二进制第2位,0表示否,1表示是
  public static final int ALLOW_INSERT = 1 << 1; // 0010
  
  // 是否允许修改,二进制第3位,0表示否,1表示是
  public static final int ALLOW_UPDATE = 1 << 2; // 0100
  
  // 是否允许删除,二进制第4位,0表示否,1表示是
  public static final int ALLOW_DELETE = 1 << 3; // 1000
  
  // 存储目前的权限状态
  private int flag;

  /**
   *  重新设置权限
   */
  public void setPermission(int permission) {
    flag = permission;
  }

  /**
   *  添加一项或多项权限
   */
  public void enable(int permission) {
    flag |= permission;
  }
  
  /**
   *  删除一项或多项权限
   */
  public void disable(int permission) {
    flag &= ~permission;
  }
  
  /**
   *  是否拥某些权限
   */
  public boolean isAllow(int permission) {
    return (flag & permission) == permission;
  }
  
  /**
   *  是否禁用了某些权限
   */
  public boolean isNotAllow(int permission) {
    return (flag & permission) == 0;
  }
  
  /**
   *  是否仅仅拥有某些权限
   */
  public boolean isOnlyAllow(int permission) {
    return flag == permission;
  }
}
详情看http://www.tuicool.com/articles/Zr6R3q

10、在图像处理方面使用比较多,比如处理颜色时,ARGB数据是一个int,
int color 
可以用
((color & 0xFF000000)>>24) & 0xFF //得到A的值 ,也就是透明通道的值
((color & 0x00FF0000)>>16) & 0xFF //R 也就是红色的值
((color & 0x0000FF00)>>8 ) & 0xFF //G 绿色的值
((color & 0x000000FF)    ) & 0xFF //B 蓝色的值 
返过来也可以通过 A R G B的值来组成一个ARGB数据来进行处理。
当你要处理图颜色时 你就发现,位移非常方便。

11、大小写转换:小写转大写:(char)('a'&~32),大写转小写:(char)('A'|32),当然我们一般还是用jdk的原生库,这里只做介绍。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值