位运算
位运算,即是 操作二进制的bit(位),来进行运算。
其分类有:按位与(&),按位或(|),按位异或(^),按位取反(~),按位左移(<<),按位右移(>>)
java中还有 “无符号右移” (>>>);C语言中是没有的
参与位运算的,应该是一个整数,或者字符型(其实质内部也是对应一个整数)
按位与(&)
按位与,是双目运算符。其功能是参与运算的两数各对应的二进位相与。只要对应的二个二进位都为1时,结果位就为1。参与运算的两个数均以补码出现。
。。。 上面说的太长了, 我都记成: 同位为1值为1,反之为0
-
获取某一段的8位
比如int类型,在当前系统中占用4个字节,1个字节等于8bit(位),即占用32位
从右数,想获取第一段的8位:
a&0xff;
获取第二段的8位:(a&0xff00)>>8
; 第三段则右移16位,第四段则右移24位。 因右移的优先级高,所以左边要用小括号,也可以:a>>8&0xff; a>>16&0xff; a>>24&0xff;
。这个应用,在安卓中的颜色类Color.java源码中能找到,用4个字节分别存储一个颜色值的:ARGB
-
&1,判断奇偶数
奇数的末位,总是为1。所以用&1来判断奇偶。
a & 1 == 1 ? "奇数\n": "偶数\n"
按位或(|)
按位或是双目运算符。同位有1得1。
-
将某一段的8位置为1
右起,第一段:
a|0xff;
第二段:a | 0xff00
… -
两个二的n次幂的整数进行 | 操作
二的n次幂整数:2, 4, 8, 16…,对应的二进制位:10, 100, 1000, 10000…
进行 | 操作后,结果就相当于: 两个整数相加的和。
若有一个操作数为负,结果就不一定满足,相加的性质。
如 -2 | 16 ,结果不为 14
按位异或(^)
按位异或是双目运算符。同位相异得1,相同得0。
-
应用1:交换两个变量的值。
int a = -9; int b = 10; a = a ^ b; b = a ^ b; a = a ^ b; printf("交换位置后,a=%d b=%d \n", a, b);
-
应用2:由于异或自身等于0,判断两个变量是否相等。
int a = 10;
int isEqual(int x) {
return (a ^ x) == 0;
}
按位取反(~)
按位取反,是单目运算符。每位1变0,0变1。 ~a;
按位左移(<<)
左移运算:左移多少位,右边就补上多少个0。也就是左移n位,就乘以2的n次冥。
a << 2
按位右移(>>)
右移,是要分符号位的。
在计算机中,不管正数还是负数,都是以补码形式存储的。正数的原码和补码是相同的,符号位为0。负数的补码,等于原码取反,再加1,符号位为1。
正数的右移:右移多少位,左边补多少个0。跟左移类似,即正数右移n位,就除2的n次冥。
负数的右移:保持符号位1不变;右移n位后,再取反码加1
a >> 2
无符号右移(>>>)
(C语言中没有这,Java中有)
正数的无符号右移:和右移是一样的。
负数的无符号右移:右移n位后,左边补n个0,符号位为0
-40 >>> 3;
40的原码: 0 0000 0000 0000 0000 0000 0000 0010 1000
40的反码: 1 1111 1111 1111 1111 1111 1111 1101 0111
-40的补码: 1 1111 1111 1111 1111 1111 1111 1101 1000
右移三位: 1 0001 1111 1111 1111 1111 1111 1111 1011
变符号位为正: 0 0001 1111 1111 1111 1111 1111 1111 1011
Java#HashMap 中的应用
HashMap有段代码
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
最终,计算出对第一个大于或等于 cap 值的 二进制数。
分析:
- n=cap-1; 这里为什么要减1, 最后说;
- 继续看,如下:
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
结果,就是对于 n,从第一位非0的二进制位开始,后面都赋上了1。
为此写了段测试代码:
public static void main(String[] args) {
int n = (int)Math.pow(2,15);
n += 2222;
get(n);
for (int i = 0; i < 5; i++) {
n |= n >>> (1 << i);
get(n);
}
}
static void get(int x) {
print("----x="+x+"----");
String bs = Integer.toBinaryString(x);
print(bs);
}
static void print(Object o) {
System.out.println(o.toString());
}
测试发现,
对于 n 的二进制中,从首个非0开始值开始,
循环第1次,即 n|= n>>>1;
可使二进制数中前2位为1;
循环第2次,即 n|= n>>>2;
可使二进制数中前4位为1;
循环第3次,即 n|= n>>>4;
可使二进制数中前8位为1;
循环第4次,即 n|= n>>>8;
可使二进制数中前16位为1;
循环第5次, 即 n|= n>>>16;
可使二进制数中前32位为1;
如,n=2^8;
二进制:1后面8个0。 经过三次循环后,前8位为1,后1位为0。第四次循环后,“可使二进制数中前16位为1”,实际 n 只有9位,所以此时 就是9个1,即2^9-1
。
- 最后看 return 语句
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
- 开始时
int n = cap - 1;
若这时 n 为负数,那么 return 1。 - 若 n 为正数,经计算后,大于等于 MAXIMUM_CAPACITY,就 return MAXIMUM_CAPACITY。
- 若 n 为正数,经计算后,小于MAXIMUM_CAPACITY, 就 return n+1;
开始时int n = cap - 1
,由上例知,若初始时cap=2^8,n=2^8-1
,最终 n+1,就是 2^8;
若初始时cap 在 [2^8 + 1, 2^9]内
,最终 n+1,就是 2^9。
简单说
要如何对一个整数,求大于它的第一个二进制数:
对于 int 型这样的32位二进制类型来说,连续n |= n >>> (1,2,4,8,16)
就可。
那若是64位,连续n |= n >>> (1,2,4,8,16,32)
就可。
位移运算和加减法的优先级
在二分查找算法中,发现 int mid = (r - l) >> 1 + l;
有问题,结果和 int mid = (r - l) / 2 + l;
不一致。
发现是 位移运算的优先级 比加减法 低。所以混合运算时,要加上小括号。
int mid = ((r - l) >> 1) + l;
这样才能保证结果正确