美文网首页
二进制位操作

二进制位操作

作者: 涉川gw | 来源:发表于2018-12-23 19:20 被阅读0次

    基础概念

    • 进制
      我们日常生活中所用的多是十进制数,例如,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,然后因为绿色和蓝色分量不在最后一个字节,所以要用掩码提取,只能先进行移位操作,将对应的颜色分量移位到最后一个字节,然后进行掩码提取数值,最终将三原色分离出来。

    总结

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

    相关文章

      网友评论

          本文标题:二进制位操作

          本文链接:https://www.haomeiwen.com/subject/jvsnkqtx.html