位运算符分为:
按位与
、按位或
、按位异或
、左移
、右移
,符号表示分别是:&
、|
、^
、<<
、>>
,在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这个状态
[本章完...]