美文网首页
Java中位运算符的运用

Java中位运算符的运用

作者: NoBugException | 来源:发表于2019-11-26 17:01 被阅读0次

    位运算符分为:按位与按位或按位异或左移右移,符号表示分别是:&|^<<>>,在Java或者Android中如果使用位运算符会提高程序的性能,因为位运算符在计算机中的计算速度是非常快的。

    在学习位运算之前,首先需要理解补码的概念,在计算机中的数值都是以补码的形式存在的,当计算机中处理数据时,其实就是处理数值的补码

    假如一个整数5,它的二进制表示是101,那么它的补码也是101,所以它在计算机中的表示是101

    那么,补码该怎么去理解?

    计算机中,没有原码反码的概念,之所以引出原码反码的概念,其实是为了简化补码的理解,纵所周知,数值分为无符号数和有符号数,可以理解为正数负数
    正数的原码、反码、补码相同;
    负数的补码等于原码的反码再加一;

    举个例子:

    • 计算5的补码

    5是正数,所以不需要计算,正数的原码、反码、补码完全一致;

    • 计算-5的补码

    假设-5的数据类型是byte(一个字节)(八位二进制)

    -5的原码:10000101(第一位代表符号位)
    -5的反码:11111010(符号位不变,剩下的7位取反)
    -5的补码:11111011(反码+1,末尾如果是1则逢二进一,如果越界则越界的高位直接舍弃)

    所以,-5在计算机中的表示是11111011

    11111011取反之后再加一,结果值和-5的原码一致,所以得出结论:补码的补码是原码

    假设-5用变量a表示,那么~a等于多少呢?

    a前面的破浪符号表示取反的意思,取反就是二进制0变成1,1变成0,包括符号位,a的原码是10000101,那么取反之后就是01111010? 这个答案是错误的,因为原码并不是计算机中的表示,我们需要将原码转成补码之后才能取反,a的补码是11111011,其反码是00000100,所以-5的反码为4。

    下面开始举例,看看在实际运用中的艺术吧。

    【计算数值的奇偶性】

    假设有一个正整数n,要求判断n是奇数还是偶数?

    • 方法一:直接除以2

    这个方法是最容易想到的方法,直接除以2就可以判断奇偶性。

    如果`(n / 2)`的值为0,则为偶数;
    如果`(n / 2)`的值为1,则为奇数;
    
    • 方法二:使用&运算符
    如果`n & 1`的值为0,则为偶数;
    如果`n & 1`的值为1,则为奇数;
    
    • 方法三:使用取模(取余)运算符%
    如果`n % 2`的值为0,则为偶数;
    如果`n % 2`的值为1,则为奇数;
    

    运算速率比较:方法一 < 方法二 < 方法三

    【乘法运算】

    假设有一个数n,要求乘以2k

    • 方法一:使用*运算符

      n * 2k

    • 方法二:使用<<运算符
    n << k
    

    【除法运算】

    假设有一个数n,要求除以2k

    • 方法一:使用/运算符

      n / 2k

    • 方法二:使用<<运算符
    n << k
    

    【交换两个数值】

    假设有两个数a,b,要求交换这两个数值?

    • 方法一:

    定义一个临时区域,int temp;

    temp = a;//将a的值放入临时区域
    a = b;//将b的值赋值给a
    b = temp;//将临时区域的值赋值给b
    
    • 方法二:采用^运算符

    异或(^)的特性:

    • 任意数和自身异或结果为0,0和任意数异或结果还是其本身
    • 符合结合律和交换律

    根据异或的特性,可以轻松交换两个变量的值

    a ^= b; //相当于 a = a ^ b
    b ^= a; //相当于 b = a ^ b
    a ^= b;//相当于 a = a ^ b
    

    【用户权限判断】(或者状态叠加的情况)

    假设有四种权限,分别是:那么如何去合理的表示用户只有一个权限或多个权限的情况,比如:

    • 我是张三:只有查询权限;
    • 我是李四:有查询、增加、修改权限;
    • 我是王五:有查询、增加、修改、删除权限;

    现在讲,四个权限分别用1、2、4、8来表示,那么张三、李四、王五三人的权限用一个整数来表示:

    • 张三:1
    • 李四:1 + 2 +4 = 7
    • 王五:1 + 2 + 4 + 8 = 15;

    那么,张三的权限为1,李四的权限为7,王五的权限为15。

    那么,重点来了

    判断张三是否有查询权限:1 & 1 = 1;
    判断张三是否有删除权限:1 & 8 = 0;
    判断李四是否有查询权限:7 & 1 = 1;
    判断李四是否有增加权限:7 & 2 = 1;
    判断李四是否有修改权限:7 & 4 = 1;
    判断李四是否有删除权限:7 & 8 = 0;
    判断王五是否有删除权限:15 & 8 = 1;
    判断王五是否有查询权限:15 & 1 = 1;
    

    1代表拥有权限,0代表没有权限。

    【一道经典面试题】

    有1000个一模一样的瓶子,其中有 999 瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。现在,你只有 10 只小白鼠和一星期的时间,如何检验出哪个瓶子里有毒药?

    这是一道算法题,考官问的是解决思路,通过二分查找来解决。

    思路如下:

    10只小白鼠,用二进制表示,第一位是第一个白鼠,第二位是第二个白鼠,第三位是第三个白鼠,依次类推...,其中0表示存活,1表示死亡,10只白鼠,代表210=1024种变化,1000个瓶子足够满足需求。

    为了便于理解,我将这道题目简化一下,如下:

    有8个一模一样的瓶子,有一瓶是毒药。任何喝下毒药的生物都会立即死亡。现在,你只有 3只小白鼠,如何检验出哪个瓶子里有毒药?

    现在给8个瓶子标号

    (1)第一个瓶子用二进制表示:000
    (2)第二个瓶子用二进制表示:001
    (3)第三个瓶子用二进制表示:010
    (4)第四个瓶子用二进制表示:011
    (5)第五个瓶子用二进制表示:100
    (6)第六个瓶子用二进制表示:101
    (7)第七个瓶子用二进制表示:110
    (8)第八个瓶子用二进制表示:111

    以上二进制只有三位,这三位从左到右分别代表三只小白鼠,所以:

    5、6、7、8瓶水混合起来为第一个样本,给第一个小白鼠喝;
    3、4、7、8瓶水混合起来为第二个样本,给第二个小白鼠喝;
    2、4、6、8瓶水混合起来为第三个样本,给第三个小白鼠喝;

    可能出现的情况有:

    (1)如果第一只小白鼠死了,那么2、4、6、8瓶水必然有一瓶有毒;

    第一只小白鼠死亡是已知条件,遍历这8个瓶子的编号,与001做&运算,如果结果为1则这瓶水有可能是毒药。

        int k = 1;//假设第一只小白鼠死亡,二进制表示为001
    
        for(int i=0;i<8;i++){//i为瓶子编号
            int n = i & k;
            if(n == 1){
                //表示当前瓶子可能含有毒药
            }
        }
    

    (2)如果第二个小白鼠死了,那么3、4、7、8瓶水必然有一瓶有毒;

        int k = 2;//假设第二只小白鼠死亡,二进制表示为010
    
        for(int i=0;i<8;i++){//i为瓶子编号
            int n = i & k;
            if(n == 1){
                //表示当前瓶子可能含有毒药
            }
        }
    }
    

    (3)如果第三个小白鼠死了,那么5、6、7、8瓶水必然有一瓶有毒;

        int k = 4;//假设第二只小白鼠死亡,二进制表示为100
    
        for(int i=0;i<8;i++){//i为瓶子编号
            int n = i & k;
            if(n == 1){
                //表示当前瓶子可能含有毒药
            }
        }
    

    (4)如果一个没死,那么第1瓶有毒;

    【用户编号的类型判断】

    在2n-1~2n-1范围内的任意两个整数&操作的结果都是两数最小的那个数,不在这个范围内的&操作都是0。(n>=1)

    • 当n=1时,范围是1~1
    • 当n=2时,范围是2~3
    • 当n=3时,范围是4~7
    • 当n=4时,范围是8~15
    • 当n=5时,范围是16~31
    • 当n=6时,范围是32~63
    • 当n=7时,范围是64~127
    • 当n=8时,范围是128~255
    • 当n=9时,范围是256~511
    • 当n=10时,范围是512~1023
    • 当n=11时,范围是1024~2047
    • 当n=12时,范围是2048~4095
    • 当n=13时,范围是4096~8191
    • 当n=14时,范围是8192~16383
    • 当n=15时,范围是16384~32767
    • 当n=16时,范围是32768~65535
    • 当n=17时,范围是65536~131071
    • 当n=18时,范围是131072~262143
    • 当n=19时,范围是262144~524287
    • 当n=20时,范围是524288~1048575
      依次类推...

    我所知道的应用场景:
    (1)一个算法不仅能生成账户的useid,还能给userid分类,比如:教授账户、老师账户、学生账户,分别对应n=18,n=19,n=20,也就是说教授userid取值范围是131072--262143,老师userid的取值范围是262144--524287,学生userid的取值范围是524288--1048575,这里假设是范围A,范围B和范围C,接下来是重点:
    取同一个范围中的任意两个数做位运算&操作,计算出来的值总是两数中的最小值;
    取不同范围中的任意两个数做位运算&操作,计算出来的值总是0;

    由于计算机默认把0当做false,把大于0的数当做true,所以这个算法在此场景下的作用有以下几点:

    1.可以作为userid的使用;
    2.可以将userid分类,并且可以用&运算符求出两个userid是否是同类,当前userid是否是教授,当前userid是否是老师,当前userid是否是学生。

    (2)可以用作状态分类,取该算法的任意几个范围,每个范围都有一系列不同的数字。
    每个范围可以被当做一种状态,每个状态又分为好几种小状态,这种尝尽用该算法还是比较方便的。

    [状态添加和移除]

    现有以下几种状态(状态不能为0,且是2次幂)

        int STATUS_1 = 1 << 0;
        int STATUS_2 = 1 << 1;
        int STATUS_3 = 1 << 2;
        int STATUS_4 = 1 << 3;
        int STATUS_5 = 1 << 4;
    

    使用按位或添加状态,如:

     flag |= STATUS_1
     flag |= STATUS_2
     flag |= STATUS_3
     flag |= STATUS_4
    

    使用按位与和按位非移除状态,如:

    flag &= ~STATUS_1
    flag &= ~STATUS_2
    flag &= ~STATUS_3
    flag &= ~STATUS_4
    

    判断是否有这个状态,如:

    a = flag & STATUS_1
    

    如果a > 0,则flag有STATUS_1这个状态(其实a = STATUS_1)
    如果a=0,则flag没有STATUS_1这个状态

    [本章完...]

    相关文章

      网友评论

          本文标题:Java中位运算符的运用

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