在位运算前,需要先了解二进制码相关知识,详情请见博主的另一篇博文:原码、反码、补码
Java定义了位运算符,应用于整数类型(int),长整型(long),短整型(short),字符型(char),和字节类型(byte)等类型。
Java包含了七种位运算符
位运算符 | 说明 |
---|---|
>> | 右移运算符,符号左侧数值 按位右移 符号右侧数值指定的位数,若为正数则高位补0,若为负数则高位补1 |
<< | 左移运算符,符号左侧数值 按位左移 符号右侧数值指定的位数,并在低位处补0 |
>>> | 无符号右移运算符,符号左侧数值 按位右移 符号右侧数值指定的位数,无论正负高位补0 |
& | 与(AND)运算符,对两个整型操作数中对应位执行布尔代数,两个位都为1时输出1,否则0 |
| | 或(OR)运算符,对两个整型操作数中对应位执行布尔代数,两个位中只要有一个为1就输出1,否则为0 |
^ | 异或(XOR)运算符,对两个整型操作数中对应位执行布尔代数,两个位相等则为0,不相等则为1 |
~ | 非(NOT)运算符,按位取反运算符翻转操作数的每一位,即0变成1,1变成0 |
七种位运算示例代码
int a = -20;
int b = 30;
int result1 = a << 1;
int result2 = a >> 1;
int result3 = a >>> 1;
int result4 = a & b;
int result5 = a | b;
int result6 = a ^ b;
int result7 = ~ a;
运算结果如下
-40
-10
2147483638
12
-2
-14
19
原理解析如下(高位0为博主手动补齐,方便观看)
a = 11111111 11111111 11111111 11101100
b = 00000000 00000000 00000000 00011110
-----------------
a << 1 --> 11111111 11111111 11111111 11011000
a >> 1 --> 11111111 11111111 11111111 11110110
a >>> 1 --> 01111111 11111111 11111111 11110110
a & b = 00000000 00000000 00000000 00001100
a | b = 11111111 11111111 11111111 11111110
a ^ b = 11111111 11111111 11111111 11110010
~a = 00000000 00000000 00000000 00010011
进行位操作时,除long型外,其他类型会自动转成int型,转换之后,可接受右操作数长度为32。进行位运算时,总是先将短整型和字节型值转换成整型值再进行移位操作的
数据类型 | 大小 |
---|---|
byte | 8 bit |
short | 16 bit |
char | 16 bit |
int | 32 bit |
long | 64bit |
示例
byte a = -128;
byte b = 63;
byte result16 = (byte)(a << 1);
byte result17 = (byte)(a >> 1);
byte result18 = (byte)(a >>> 1);
byte result19 = (byte)(a & b);
byte result20 = (byte)(a | b);
byte result21 = (byte)(a ^ b);
byte result22 = (byte)(~ a);
上面的代码在位运算后类型自动提升为了int,所以需要使用int类型的变量来接受,但是我们可以在进行位运算后进行强转,但强转会直接截取字节,从而导致丢失精度,最终得到的结果如下
0
-64
-64
0
-65
-65
127
对于 int 类型的整数移位 a >> b, 当 b>32 时,系统先用 b 对 32 求余(因为 int 是 32 位),得到的结果才是真正移位的位数,例如,a >> 33 和 a >> 1 的结果相同,而 a >> 32 = a
知道了位运算的规则,那么我们什么时候可以使用到位运算?位运算的使用场景有哪些呢?
位运算的使用场景如下:
public void method1(int a){
if (a&1 == 0) {
log.info("偶数")
}else if ( a&1 == 1) {
log.info("奇数")
}
}
偶数的最低位肯定是0,奇数的最低位肯定是1,而1的最低位是1其他位都为零,当进行与运算时:
public void swap(int a , int b) {
a = a ^ b;
b = b ^ a;
a = a ^ b;
}
这里需要知道两点:
好的,那么上面代码操作就等于:
a = a ^ b;
b = b ^ a = b ^ (a ^ b) = a;
a = a ^ b = (a ^ b) ^ (b ^ (a ^ b)) = (a ^ b) ^ a = b;
public boolean power2(int a) {
if (a <= 0){
System.out.println("这里不计算负数,直接返回false");
return false;
} else{
return (a&(a-1))==0
}
}
任何正整数如果是2的幂数,都形如下
10
100
1000
10...0
即首位都为1,往后位数都为0,那么在减去1后又都形如下
01
011
0111
01...1
所以大于零的2的幂数和自己减一后的数进行与运算结果必然为0
public int average(int a, int b){
return (a&b)+((a^b)>>1);
}
具体原理详见博主的另一篇博文:位运算求整数的平均值
public int abs( int a ) {
return (a + (a >> 31)) ^ (a >> 31) ;
}
体原理详见博主的另一篇博文:位运算求整数的绝对值
注意,这里只支持正数的取模操作
a % (2^n) = a & (2^n - 1)
取模得到的值肯定小于模数,即
11%2 那么得到的值肯定小于2,也就是[0-2)之间, 00000000 - 00000001
101%8 那么得到的值肯定小于8,也就是[0,8)之间, 00000000 - 00000111
---------------------
现在我们再来看一下具体的模操作
11%2=1
00001011
%
00000010
得到
00000001
101%8=5
01100101
%
00001000
得到
00000101
---------------------
从上面的例子我们可以看到一个现象,一个数对2的幂数的模,其实就是被模数按照模数的位数的截取
即
如果模数是8
1000
那么不管被模数是多少,它的模都为被模数后三位所代表的值
如果被模数为20
10010
那么它的模就是010,也就是2
---------------------
当然,有一种另类情况,就是被模数小于模数,那么它们的模就等于被模数本身
例如:2%8=2
那么通过什么方式可以获取被模数的需要截取的位数呢?正好,与操作就可以做到,那为什么需要-1呢?
因为2的幂数-1永远都是形如下面的格式
1
11
111
1...1
真好是这个特性保证了a & (b-1)能够获得后几位的值,所以
a % b = a & (b-1)
a * (2^n) = a < < n
a / (2^n) = a >> n
就是使用一次异或加密,使用两次异或解密
public void test4(){
String a = "sadfsdfsdfhfghf123dfgfg";
System.out.println(a);
int key = 324545231;
byte[] bytes = a.getBytes();
for (int i = 0; i < bytes.length-1; i++) {
bytes[i] = (byte)(bytes[i] ^ key);
}
String b = new String(bytes);
System.out.println(b);
for (int i = 0; i < bytes.length-1; i++) {
bytes[i] = (byte)(bytes[i] ^ key);
}
String c = new String(bytes);
System.out.println(c);
}
打印结果
sadfsdfsdfhfghf123dfgfg
����������������������g
sadfsdfsdfhfghf123dfgfg
可以参考JDK源码:
Spliterator(当中定义了许多Characteristic,包括ORDERED、DISTINCT、SORTED等等),在集合类在使用到它的时候,会定义不同集合拥有不同的Characteristic,详情见
ArrayList.ArrayListSpliterator的characteristics方法
HashMap.KeySpliterator的characteristics方法
...