位运算案例
总结: Java位运算是针对于整型(byte、char、short、int、long)数据类型的二进制进行的移位操作。
位运算认知*
数据类型 |
位数 |
---|---|
byte |
8 |
boolean |
8 |
short |
16 |
int |
32 |
long |
64 |
float |
32 |
double |
64 |
char |
16 |
位运算符认知*
优先级S:~ 波浪
优先级A:<<、>>和>>>
优先级B:&
优先级C:^
优先级D:|
位运算符初使用*
场景1:判断奇偶数 a&1 结果为 0 ,a就是偶数 结果为 1 ,a就是奇数 (奇数第一位肯定是1)
int c = 8;
// 8 = 0000 1000
// 1 = 0000 0001
// & = 0000 0000
System.out.println(c & 1); //0
c = 129;
// 129 = 1000 0001
// 1 = 0000 0001
// & = 0000 0001
System.out.println(c & 1); //1
场景2:求平均数 (x+y)/2 这样吗?考虑过 x+y可能超过int的范围吗?正确的姿势是 (x&y)+((x^y)>>1)
// x+y = 奇数,例137,该算法 = 68; x+y = 偶数,例136,该算法 = 68;
场景3:有两个int类型变量x、y,要求两者数字交换,不用临时变量?x ^= y; y ^= x; x ^= y;
int x = 8; //0000 1000 int y = 128; //1000 0000 //x^=y这个表达式相当于x=x^y // x = 0000 1000 // y = 1000 0000 // ^ = 1000 1000 x = 136 x ^= y; //y^=x这个表达式相当于y=y^x // y = 1000 0000 // x = 1000 1000 // ^ = 0000 1000 y = 8 y ^= x; //x^=y这个表达式相当于x=x^y // x = 1000 1000 // y = 0000 1000 // ^ = 1000 0000 x = 128 x ^= y; System.out.println(x); //128 System.out.println(y); //8
场景4:求绝对值 ( 绝对值是一种运算,运算符号通常用||表示。这种运算的意义是:一个正数和0的绝对值是它本身,一个负数的绝对值是它的相反数。总之,一个数的绝对值是非负数.)
int abs( int x ) {
int y= x >> 31 ;
return (x^y)-y ; //or: (x+y)^y
}
//计算机表示负数:需要使用“二进制的补数” // 为了获得补数,需要将二进制的各数位数值全部取反,再讲结果+1 //8 = 0000 0000 0000 0000 0000 0000 1000 //取反 = 1111 1111 1111 1111 1111 1111 0111 // +1 = 1111 1111 1111 1111 1111 1111 1000 int xx = -8; //1111 1000 //按位右移31位: 1111 1111 1111 1111 1111 1111 1111 int yy = xx >> 31; System.out.println(yy); //-1 //xx = 1111 1111 1111 1111 1111 1111 1000 //yy = 1111 1111 1111 1111 1111 1111 1111 //(xx^yy) = 0000 0000 0000 0000 0000 0000 0111 = 7 //(xx^yy)-yy = 7-(-1) = 8 System.out.println((xx^yy)-yy); //-8
场景8:求相反数 (~x+1)
int x = 8; //0000 1000
//~ = 1111 0111 = -9
// -9 + 1 = -1
System.out.println(~x+1); //-8
场景9:指定位数比较
//处理整数类型,可以直接对组成整数类型的各个位进行操作。这意味着可使用掩码技术(转二进制)得到整数中的各个位: //十进制8 转换二进制 = 00001000 int n = 8; //0b开头表二进制; 0开头表八进制; 0x开头表十六进制; //ob10000000二进制字面量默认为int 四个字节 = 128; (byte)ob10000000 = -128 //变量n 二进制后的第四位 = 1, 则返回1,否返回0 int a = (n & 0b1000) / 0b1000; System.out.println(a); //输出1
位运算符:逻辑推理题 - 小白鼠试毒*
有1000瓶水,其中有一瓶有毒,小白鼠只要尝一点带毒的水24小时后就会死亡,问至少要多少只小白鼠才能在24小时鉴别出哪瓶水有毒,刚看类似的问题,一般人一定是一脸懵逼的状态的,分析一下,喝了带毒的水24小时才会毒发,目前需求是要24小时就要鉴别出来哪瓶有毒,所以小白鼠只够喝一次水的时间,完全摸不到头脑……
解决办法
给1000个瓶分别标上如下标签(10位长度): 0000000001 (第1瓶) 0000000010 (第2瓶) 0000000011 (第3瓶) ...... 1111101000 (第1000瓶) 从编号最后1位是1的所有的瓶子里面取出1滴混在一起(比如从第一瓶,第三瓶,。。。里分别取出一滴混在一起)并标上记号为1。以此类推,从编号第一位是1的所有的瓶子里面取出1滴混在一起并标上记号为10。现在得到有10个编号的混合液,小白鼠排排站,分别标上10,9,。。。1号,并分别给它们灌上对应号码的混合液。24小时过去了,过来验尸吧: 从左到右,死了的小白鼠贴上标签1,没死的贴上0,最后得到一个序号,把这个序号换成10进制的数字,就是有毒的那瓶水的编号。 检验一下:假如第一瓶有毒,按照0000000001 (第1瓶),说明第1号混合液有毒,因此小白鼠的生死符为0000000001(编号为1的小白鼠挂了),0000000001二进制标签转换成十进制=1号瓶有毒;假如第三瓶有毒,0000000011 (第3瓶),第1号和第2号混合液有毒,因此小白鼠的生死符为00000011(编号为1,2的鼠兄弟挂了),0000000011二进制标签转换成十进制=3号瓶有毒。
解决思路
逻辑上来解释这个问题,首先看瓶子,二进制数,10位能够表示的最大数位2的10次方-1=1023,大于1000,所以足够覆盖1000以内的二进制展示,有毒的那瓶水的二进制插入在竖直排列的数据中,混合液为获取当前二进制位上为1的所有水的混合,当小白鼠喝了over以后,说明有毒的水当前二进制位上的数值是1,否则,说明当前二进制的数值为0,所以当小白鼠最终是否over,根据转换后的十进制就能获取到有毒的那瓶水。
项目实战*
例如,在一个系统中,用户一般有查询(Select)、新增(Insert)、修改(Update)、删除(Delete)四种权限,四种权限有多种组合方式,也就是有16中不同的权限状态(2的4次方)。
public class Permission { // 是否允许查询 private boolean allowSelect; // 是否允许新增 private boolean allowInsert; // 是否允许删除 private boolean allowDelete; // 是否允许更新 private boolean allowUpdate; // 省略Getter和Setter } |
//下面是另外一种方式,使用位掩码的话,用一个二进制数即可,每一位来表示一种权限,0表示无权限,1表示有权限。
ALLOW_SELECT = 1 << 0 转成二进制就是0001,二进制第一位表示Select权限。 ALLOW_INSERT = 1 << 1 转成二进制就是0010,二进制第二位表示Insert权限。
private int flag存储了各种权限的启用和停用状态,相当于代替了Permission中的四个boolean类型的变量。
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; // 相当于flag = 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; } } |
flag的四个二进制位来表示四种权限的状态,每一位的0和1代表一项权限的启用和停用,下面列举了部分状态表示的权限:
flag |
删除 |
修改 |
新增 |
查询 |
|
1(0001) |
0 |
0 |
0 |
1 |
只允许查询(即等于ALLOW_SELECT) |
2(0010) |
0 |
0 |
1 |
0 |
只允许新增(即等于ALLOW_INSERT) |
4(0100) |
0 |
1 |
0 |
0 |
只允许修改(即等于ALLOW_UPDATE) |
8(1000) |
1 |
0 |
0 |
0 |
只允许删除(即等于ALLOW_DELETE) |
3(0011) |
0 |
0 |
1 |
1 |
只允许查询和新增 |
0 |
0 |
0 |
0 |
0 |
四项权限都不允许 |
15(1111) |
1 |
1 |
1 |
1 |
四项权限都允许 |
使用位掩码的方式,只需要用一个大于或等于0且小于16的整数即可表示所有的16种权限的状态。
//调用这个方法可以在现有的权限基础上添加一项或多项权限。 public void enable(int permission) { flag |= permission; // 相当于flag = flag | permission; } |
//添加一项Update权限: permission.enable(NewPermission.ALLOW_UPDATE); |
假设现有权限只有Select,也就是flag是0001。执行以上代码,添加Update权限,flag = 0001 | 0100,也就是0101,便拥有了Select和Update两项权限。
解释: 查看权限 0001
修改权限 0100
flag = 0101 //对应位都是0,则为0,否则为1
//添加Insert、Update、Delete三项权限:
permission.enable(NewPermission.ALLOW_INSERT | NewPermission.ALLOW_UPDATE | NewPermission.ALLOW_DELETE);
//运算结果 flag = 1110
两者对比
设置仅允许Select和Insert权限
//Permission类 permission.setAllowSelect(true); permission.setAllowInsert(true); permission.setAllowUpdate(false); permission.setAllowDelete(false); |
//NewPermission类 permission.setPermission(NewPermission.ALLOW_SELECT | NewPermission.ALLOW_INSERT); |
判断是否同时有Select和Insert、Update权限
//Permission类 if (permission.isAllowSelect() && permission.isAllowInsert() && permission.isAllowUpdate()) |
//NewPermission类 if (permission.isAllow (NewPermission.ALLOW_SELECT | NewPermission.ALLOW_INSERT | NewPermission.ALLOW_UPDATE)) |
/**
* 是否拥某些权限
*/
public boolean isAllow(int permission) { //permission = 0111
// 假设flag = 0111
// (flag & permission) = 0111 & 0111 = 0111 //&相对位都为1,则为1,否则为0
// 0111 = 0111 返回true
return (flag & permission) == permission;
}
源码*
java.lang.reflect.Modifier
在Java反射中,java.lang.reflect.Modifier是用来判断类、成员变量、方法等包含的修饰符。在Modifier的源代码中可以看到:
public static final int PUBLIC = 0x00000001; public static final int PRIVATE = 0x00000002; public static final int PROTECTED = 0x00000004; public static final int STATIC = 0x00000008; public static final int FINAL = 0x00000010; public static final int SYNCHRONIZED = 0x00000020; // ...... public static boolean isProtected(int mod) { return (mod & PROTECTED) != 0; } public static boolean isStatic(int mod) { return (mod & STATIC) != 0; }
以上资料来源:
https://www.cnblogs.com/yuanhailiang/p/9479105.html
http://xxgblog.com/2013/09/15/java-bitmask/