java位运算
位运算表达式由操作数和位运算符组成,实现对整数类型的二进制数进行位运算。位运算符可以分为逻辑运算符(包括~
、&
、|
和^
)及移位运算符(包括>>
、<<
和>>>
)。
二进制表示
二进制第一位为符号位,0表示正数,1表示负数。
1.正数表示,例如:int类型值 5
(int用32位表示)
5 = 0000 0000 0000 0000 0000 0000 0000 0101
2.负数表示,例如:int类型值 -5
-5 = 1111 1111 1111 1111 1111 1111 1111 1011
负数二进制表示方法如下:
首先来个正数5
5 = 0000 0000 0000 0000 0000 0000 0000 0101
这个就是原码
,然后原码
取反(0的变成1,1的变成0),得到反码
原码: 0000 0000 0000 0000 0000 0000 0000 0101
反码: 1111 1111 1111 1111 1111 1111 1111 1010
反码
进行加1得到补码
原码: 0000 0000 0000 0000 0000 0000 0000 0101
反码: 1111 1111 1111 1111 1111 1111 1111 1010
补码: 1111 1111 1111 1111 1111 1111 1111 1011
最终得到的补码
就是负数在计算机中的二进制表示方法
3.二进制转换为十进制
正数不讲,略…, 这里只说负数,先随便来一个负数二进制
负数二进制: 1111 1111 1111 1111 1111 1111 1111 1011
负数二进制,减1
负数二进制: 1111 1111 1111 1111 1111 1111 1111 1011
二进制减一: 1111 1111 1111 1111 1111 1111 1111 1010
取反
负数二进制: 1111 1111 1111 1111 1111 1111 1111 1011
二进制减一: 1111 1111 1111 1111 1111 1111 1111 1010
减一后取反: 0000 0000 0000 0000 0000 0000 0000 0101
然后计算得到结果为5,那么负数二进制就为-5
逻辑运算符
1.非(~
)运算 : 取反
System.out.println(~5);// 结果为-6
5 = 0000 0000 0000 0000 0000 0000 0000 0101 = 5
~5 = 1111 1111 1111 1111 1111 1111 1111 1010 = -6
2.与 (&
)运算 : 有0出0;全1出1
System.out.println(5 & 3);// 结果为1
5 = 0000 0000 0000 0000 0000 0000 0000 0101 = 5
3 = 0000 0000 0000 0000 0000 0000 0000 0011 = 3
5 & 3 = 0000 0000 0000 0000 0000 0000 0000 0001 = 1
3.或(|
)运算 : 有1出1;全0出0
System.out.println(5 | 3);// 结果为7
5 = 0000 0000 0000 0000 0000 0000 0000 0101 = 5
3 = 0000 0000 0000 0000 0000 0000 0000 0011 = 3
5 | 3 = 0000 0000 0000 0000 0000 0000 0000 0111 = 7
4.异或(^
)运算: 相同得0;相异得1
System.out.println(5 ^ 3);//结果为6
5 = 0000 0000 0000 0000 0000 0000 0000 0101 = 5
3 = 0000 0000 0000 0000 0000 0000 0000 0011 = 3
5 ^ 3 = 0000 0000 0000 0000 0000 0000 0000 0110 = 6
小应用
A. 一个数字标识位表示多种逻辑,例如:一个任务可分成n个独立小任务,每个小任务之间不分先后顺序,当所有小任务完成则表示此任务完成。比较用 &
,加用 |
如果n=3,则将3个小任务分别用$2^0$ , $2^1$ , $2^2$即1,2,4表示
//更新当前任务完成第1个小任务
update tbl_task set status = status|1 where id=1;
//同理,更新当前任务完成第2个小任务
update tbl_task set status = status|2 where id=1;
//同理,更新当前任务完成第3个小任务
update tbl_task set status = status|4 where id=1;
//当status = 7时,则表示三个任务都完成;
//查询第1个小任务未完成的所有任务
select * from tbl_task where status&1=0;
//查询第1个小任务已完成的所有任务
select * from tbl_task where status&1=1;
//同理,第2个小任务
//查询第2个小任务未完成的所有任务
select * from tbl_task where status&2=0;
//查询第2个小任务已完成的所有任务
select * from tbl_task where status&2=2;
B. 利用异或运算的自反性交换两个数
public void exchange(int a, int b) {
a ^= b;
b ^= a;
a ^= b;
}
C.一个数组存放若干整数,一个数出现奇数次,其余数均出现偶数次,找出这个出现奇数次的数?
最直接的办法就是把所有数异或 (奇数个异或是本身,偶数个是0)
D.1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?
解法一:将所有数加起来,减去1+2+…+1000的和。
这个算法已经足够完美了,相信出题者的标准答案也就是这个算法,唯一的问题是,如果数列过大,则可能会导致溢出。 解法二:异或就没有这个问题,并且性能更好。将所有的数全部异或,得到的结果与1^2^3^…^1000的结果进行异或,得到的结果就是重复数。
解法一很显然,解法二需要证明一下:
前面提到异或具有交换律和结合律,所以1^2^…^n^…^n^…^1000^,无论这两个n出现在什么位置,都可以转换成为1^2^…^1000^(n^n)的形式。 其次,对于任何数x,都有x^x=0,x^0=x。 所以1^2^…^n^…^n^…^1000 = 1^2^…^1000^(n^n)= 1^2^…^1000^0 = 1^2^…^1000(即序列中除了n的所有数的异或)。 令,1^2^…^n^..^1000(序列中包含一个n)的结果为T 则1^2^..^n^..^n^..^1000^(序列中包含2个n)的结果就是T^n。 T^(T^n)=n。 所以,将所有的数全部异或,得到的结果与1^2^3^…^1000的结果进行异或,得到的结果就是重复数。
移位运算符
1.左移位运算符(<<
)能将运算符左边的运算对象向左移动运算符右侧指定的位数(在低位补0)。
System.out.println(5 << 3);// 运行结果是40
5 = 0000 0000 0000 0000 0000 0000 0000 0101 = 5
5 << 3 = 0000 0000 0000 0000 0000 0000 0010 1000 = 40
2.“有符号”右移位运算符(>>
)则将运算符左边的运算对象向右移动运算符右侧指定的位数。 “有符号”右移位运算符使用了“符号扩展”:若值为正,则在高位插入0;若值为负,则在高位插入1。
System.out.println(5 >> 3);// 运行结果是0
5 = 0000 0000 0000 0000 0000 0000 0000 0101 = 5
5 >> 3 = 0000 0000 0000 0000 0000 0000 0000 0000 = 0
System.out.println(-5 >> 3);// 结果是-1
-5 = 1111 1111 1111 1111 1111 1111 1111 1011 = -5
-5 >> 3 = 1111 1111 1111 1111 1111 1111 1111 1111 = -1
3.Java也添加了一种“无符号”右移位运算符(>>>
),它使用了“零扩展”:无论正负,都在高位插入0。这一运算符是C或C++没有的。
System.out.println(5 >>> 3);// 结果是0
5 = 0000 0000 0000 0000 0000 0000 0000 0101 = 5
5 >>> 3 = 0000 0000 0000 0000 0000 0000 0000 0000 = 0
System.out.println(-5 >>> 3);// 结果是536870911
-5 = 1111 1111 1111 1111 1111 1111 1111 1011 = -5
-5 >>> 3 = 0001 1111 1111 1111 1111 1111 1111 1111 = 536870911