Java位运算

在位运算前,需要先了解二进制码相关知识,详情请见博主的另一篇博文:原码、反码、补码


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

知道了位运算的规则,那么我们什么时候可以使用到位运算?位运算的使用场景有哪些呢?

位运算的使用场景如下:

1. 判断奇偶性

public void method1(int a){
	if (a&1 == 0) {
		log.info("偶数")
	}else if ( a&1 == 1) {
		log.info("奇数")
	}
}

偶数的最低位肯定是0,奇数的最低位肯定是1,而1的最低位是1其他位都为零,当进行与运算时:

  • 偶数必然:a&1 == 0
  • 奇数必然:a&1 == 1

2. 不使用中间变量交换两个数

public void swap(int a , int b) { 
    a = a ^ b;
    b = b ^ a;
    a = a ^ b;
} 

这里需要知道两点:

  1. 任何数和自己进行异或操作结果都为0
  2. 异或符合交换律,即a ^ b = b ^ a

好的,那么上面代码操作就等于:

a = a ^ b;
b = b ^ a = b ^ (a ^ b) = a;
a = a ^ b = (a ^ b) ^ (b ^ (a ^ b)) = (a ^ b) ^ a = b;

3. 判断一个正整数是不是2的整数次幂

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

4. 计算整数的平均值

public int average(int a, int b){    
    return (a&b)+((a^b)>>1); 
} 

具体原理详见博主的另一篇博文:位运算求整数的平均值

5. 计算绝对值

public int abs( int a ) { 
	return (a + (a >> 31)) ^ (a >> 31) ;
}

体原理详见博主的另一篇博文:位运算求整数的绝对值

6. 取模运算转化成位运算 (在不产生溢出的情况下)

注意,这里只支持正数的取模操作

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)

7. 乘法运算转化成位运算 (在不产生溢出的情况下)

a * (2^n) = a < < n 

8. 除法运算转化成位运算 (在不产生溢出的情况下)

a / (2^n) = a >> n 

9.对称加密

就是使用一次异或加密,使用两次异或解密

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

10.源码应用

可以参考JDK源码:
Spliterator(当中定义了许多Characteristic,包括ORDERED、DISTINCT、SORTED等等),在集合类在使用到它的时候,会定义不同集合拥有不同的Characteristic,详情见

ArrayList.ArrayListSpliterator的characteristics方法
HashMap.KeySpliterator的characteristics方法
...

你可能感兴趣的:(编程语言)