二进制位操作

基础概念

  • 进制
    我们日常生活中所用的多是十进制数,例如,1286是一个四位十进制数,我们可以把这个数拆开成:
    1x1000+2x100+8x10+6x1 ==>(等价于)1x103+2x102+7x101+6x100
    可以看出,十进制就是以10的幂次方为基数换算过来的,那么同理,二进制就是以2为幂次方,例如,二进制数1011换算为十进制数就是:
    1x23+0x22+1x21+1x20=11
    因为计算机的电路是只有两种状态的,所以二进制非常适合计算机使用。
  • 二进制整数
    一个字节通常来说是包括八个位的,所以,对应的二进制数位数为八,表示的范围为256:
    11111111==》1x27+1x26+1x25+....+1x20=255
    在表示有符号整数的情况下,通常将最高位表示符号位,所以此时表示的范围变为-128~127,负数时最高位为1,整数时为0.

位运算

  • 取反 ~
    取反运算符将二进制数每个位为1的变成0,为0的变成1,例如:
~ (10101)  //原值
  (01010) //取反结果值

值得注意的是取反运算不改变原变量的值,假如一个变量var,赋值为2(八位二进制中表示为00000010),于是~var的运算结果为(11111101),但是var仍为2,所以如果想使用运算后的结果,需要进行赋值,比如:

newvar=~var;
//或
var =~var;
  • 位与 :&
    二进制于运算&,会将两个操作数的二进制位逐位进行比较,相同位的值都为1时,运算结果对应位的值才为1,否则为0,例如:
  10011  //操作数1
 &10101 //操作数2
  10001 //结果
  • 位或 |
    跟位与操作类似,不过或操作只需两个比较数的对应位其中一个为1,则结果位就为1:
 1011 //操作数1
| 1101 //操作数2
  1111 //结果
  • 异或 ^
    二进制异或操作对两个操作数按位进行比较,对于结果,当两个操作数不一样时,结果位为1,如果两个操作数对应位同为0或同为1,则结果位0,例如:
  10101  //操作数1
^ 11011 // 操作数2
  01110 //结果

位操作的用法

  • 掩码
    掩码(英语:Mask)在计算机学科及数字逻辑中指的是一串二进制数字,通过与目标数字的按位操作,达到屏蔽指定位而实现需求。二进制位操作中通常使用与&操作实现掩码,我们可以定义常量MASK=2,即二进制00000010,待操作数flag=11,即00001011,那么:
    flag & MASK =>
    00000010
&   00001011
    00000010

可以看到,flag除了位1外,其他都被置0了,可以看出掩码的一个作用就是保留掩码为1 的位所对应操作数的位为1,其他位清零。

  • 打开位
    有时候我们需要将一个二进制数中的某个位置一,比如10000101中,第一位控制着硬件扬声器的开关,当我们想打开扬声器时,只需进行如下操作,假的MASK=00000010,flag=10000101,
    flag=flag | MASK
    flag就变为10000111.
  • 位关闭
    与位打开相反,位关闭操作可以将某一个位置0而不影响其他位,具体操作为
flag=flag & ~MASK
或者
flag&= ~MASK

同样,MASK保存着需要置0的位,对MASK取反就得到了一个除了待置0位为0,其他位均为1的二进制数:11111101,
然后与操作数进行与操作,就能将待置0位变成0.

  • 转置
    转置操作可以将一个为原来为0(或1)的位变为1(或0),具体操作如下:
    flag= flag ^ MASK
    MASK=00000010,flag=10000101,
  00000010
^ 10000101
  10000111
移位运算
  • 左移:<<
    左移运算将操作数按位全部往左移动对应个位,右边空出来的位用0填充,左边移出的位丢弃掉,比如:
    10001010 << 2
    00010100
    左移操作中,无符号数左移移位相当于乘以21,左移两位相当于乘上22;而对于有符号数,负数跟正数是不一样的,计算机中,负数的二进制是使用补码来表示的,最高位存放符号,正数为0,负数为1.那么什么是补码呢,举个栗子:
1000 0101 //原码,原码就是一个数的二进制表示,
          //例如这个数是-5的八位二进制表示,高位的1表示是负数
1111 1010 //反码,反码就是一个二进制数的原码,除符号位以外,其余
         // 位取反
1111 1011 //补码,补码就是在反码的基础上加1

所以,对于负数的左移,需要先求出补码,然后用补码来左移,然后再求回原码,就得出了负数左移的结果,例如:上面的-5的补码为1111 1011 ,左移一位后得1111 0110,求这个的原码得:1000 1010 ,即为-10。

  • 右移
    对于右移,由于会涉及到符号位,所以会分有符号右移和无符号右移,在java中分别用>> 和>>>符号来区分。有符号右移时,若为负数,高位将补1,无符号右移无论正负,高位均补0。例如:
    4>>2
    0000 0100 >>2 =0000 0001,结果为1;
    -4>>2
    1000 0100 >>2 ,先求反码:1111 1011 ,加1求补码:1111 1100,右移 1111 1111 ,结果数原码:1000 0001,得-1,
    对于有符号右移也是同理,只是将高位补0即可。

实战例子

在读android或java等的源码的时候,我们会看到大量的位运算操作,一开始我是一脸懵逼的,搞不清楚这是什么操作,也不懂为什么要这样操作,看到多了觉得大神都这么玩,肯定是有好处的(位操作会快很多),所以也就决定研究一下其中的原理,接下来就上面所记录的知识来写一个“源码”级别的demo,开整:
test.java

int BYTE_MASK=0xff;
long color=0x002a162f;
int blue,green,red;
red=color & BYTE_MASK;
green=(color >> 8) & BYTE_MASK;
blue =(color >> 16) & BYTE_MASK;
log.d("red="+red+" green="+green+" blue="+blue);

上面这个例子中,color存放的是一个颜色的十六进制值,这个颜色分为三原色,红绿蓝,最低位字节存放红色分量,上一个字节存放绿色分量,第三个字节放蓝色分量,BYTE_MASK作为掩码,上面有介绍过,掩码可以将自身标志位以外的位清除,所以只要用掩码和操作数进行&操作,就能提取出掩码位的数据,上面red=color & BYTE_MASK;就是将color的最后一个字节提取出来,赋值给red,然后因为绿色和蓝色分量不在最后一个字节,所以要用掩码提取,只能先进行移位操作,将对应的颜色分量移位到最后一个字节,然后进行掩码提取数值,最终将三原色分离出来。

总结

位操作不仅能装逼,还能很好的提高程序的性能,是我们进阶中高级码农的必备技能啊,在这记录下,以备岁月摧残记性。

你可能感兴趣的:(二进制位操作)