一、前言
当涉及位操作和位级运算时,Java 提供了一组特殊的运算符,即左移(<<
)和右移(>>
)运算符。这些运算符与普通的算术和逻辑运算符不同,它们操作的是数字的二进制位。位操作是计算机底层编程中常用的技巧,能够高效地执行一些操作,如数值乘除以2的幂次方,处理标志位,以及在一些位级编码场景下进行数据处理。
左移和右移运算符用于将二进制位向左或向右移动指定的位数,从而实现对数值的快速操作。左移操作将数值的二进制表示向左移动,等效于乘以2的幂次方,而右移操作将数值的二进制表示向右移动,等效于除以2的幂次方。这些运算符在处理大数据集、位掩码、网络协议以及嵌入式系统等领域都具有广泛的应用。
然而,需要注意的是,位操作并不常见于一般的业务逻辑编程,而更多地出现在需要对二进制数据进行处理的底层系统级编程中。在使用位操作时,应该清晰地理解位操作的语义,防止出现意外错误。同时,要考虑到可读性和代码维护性,不应滥用位操作,而是在合适的场景下使用,以提高代码的效率和性能。
二、Java的左移、右移运算符
<<
是左移;如:<<1
就是左移一位;<<2
就是左移两位>>”
是右移;如:>>1
就是右移一位;>>2
就是右移两位
记忆技巧: 尖括号的方向朝向哪就是哪移。
左移就是将目标转换为二进制后,从右向左移动一位,右边补零,左边移除。
右移就是将目标转换为二进制后,从左向右移动一位,左边补零,右边移除。。
三、Java中左移、右移使用场景
可以代替乘(左移)和除(右移),如下:
package zd.hjd.test.synchronizedtest; public class MyTestMain { public static void main(String[] args) { System.out.println(4*2); System.out.println(4<<1); System.out.println(4/2); System.out.println(4>>1); } }
输出结果:
8
8
2
2
四、使用左移、右移优点和弊端
4.1 优点
使用左移(<<)和右移(>>)的好处在于它们可以在某些情况下提供更高效的数值操作,特别是涉及到2的幂次方的操作。以下是一些使用左移右移的好处:
- 速度优势: 左移和右移操作是位级操作,它们在底层直接对二进制数据进行移动,相比于使用乘除法运算符,位移操作可以更快地完成数值的变换。这在一些需要高效计算的场景下尤为重要。
- 数值操作: 左移和右移可以用于实现数值的倍增或减少。例如,将一个数值左移n位,等效于将其乘以2的n次方;将一个数值右移n位,等效于将其除以2的n次方。
- 位掩码操作: 在位掩码操作中,可以使用左移来设置位,使用右移来读取位。这在一些标志位、开关状态的处理中非常有用。
- 位级编码: 在某些编码和解码场景下,需要将信息编码到特定的位上,或者从特定位上解码信息。左移和右移操作可以用于实现这种位级的数据处理。
- 嵌入式系统: 在嵌入式系统中,资源有限,效率至关重要。位移操作可以提供更紧凑和高效的代码,减少资源消耗。
4.2 弊端
- 可读性下降: 左移和右移操作对于非熟悉位操作的开发者来说可能不太直观,代码的可读性可能会下降,使得代码难以理解和维护。
- 位移越界: 在使用右移操作时,如果对有符号的整数进行右移,可能会导致符号位扩展,从而改变数值的意义。例如,对于负数的右移可能会导致意外的结果。
- 移位溢出: 如果使用左移操作将某个位移到超出数据类型的范围,就会发生移位溢出,导致结果不正确。
- 代码依赖于底层实现: 使用位操作可能会使代码与底层硬件或编译器的实现相关,导致代码的可移植性下降。
- 复杂逻辑错误: 如果没有正确地处理边界情况,可能会导致复杂的逻辑错误,尤其是在涉及位级运算的复杂算法中。
- 难以维护: 过度使用位操作可能使代码变得难以理解和维护,特别是在逻辑复杂的情况下。
- 代码不易调试: 位操作可能会导致一些难以调试的问题,因为它们涉及底层位级表示,而不是更常见的数值操作。
五、使用左移、右移注意事项
1.带符号左移需要考虑移动后数字变为负数的情况,因为二进制的首位为符号位。
2.从上述中我们知道,左移就是将一个数字的二进制编码向左边移动,右边补零,右移相反。如果一个int
类型的数字向左移32位结果是不是就是0呢?答案是错误的。当int类型数字进行左移,并且左移位数大于等于32位时,会先与32求余,然再进行移动。32%32 = 0,使用int
类型向左移32位相当于不移动,byte
和short
类型同理。long
类型是64位。
public class MoveLeft { public static void main(String[] args) { int a = 10; byte b = 50; short s = 60; System.out.println(a >> 1);// 5 System.out.println(a << 1);// 20 System.out.println(a << 32);// 10 System.out.println(a << 33);// 20 System.out.println(b << 32);// 50 System.out.println(b << 33);// 100 System.out.println(s << 32);// 60 System.out.println(s << 33);// 120 long longValue = 733183670L; System.out.println("longValue:" + (longValue));//打印longValue System.out.println("longValue左移1位:" + (longValue << 1));//左移1位 System.out.println("longValue左移8位:" + (longValue << 8));//左移8位 //当long类型左移位数大于等于64位操作时,会先求余后再进行移位操作 System.out.println("longValue左移64位:" + (longValue << 64));//求余为64%64=0,相当于左移0位(不移位) System.out.println("longValue左移72位:" + (longValue << 72));//求余为72%64=8,相当于左移8位 System.out.println("longValue左移128位:" + (longValue << 128));//求余为128%64=0,相当于左移0位(不移位) } }
3.double和float不能使用位移来操作,编译不通过。
六、无符号右移>>>
无符号右移和右移原理一样,只不过是右移后在左边补零后舍弃掉符号位。如下所示:
public class MoveLeft { public static void main(String[] args) { int a = -10; System.out.println(a >>> 1);// 结果是 2147483643 } }
执行的过程如下:
//向得出-10的二进制,需要得到10的二进制 0000 0000 0000 0000 0000 0000 0000 1010// 10 二进制如下 1111 1111 1111 1111 1111 1111 1111 0101// 10 的反码 1111 1111 1111 1111 1111 1111 1111 0110 //反码+1 就为 -10 的二进制 0111 1111 1111 1111 1111 1111 1111 1011// 右移一位 //转换为10进制 为 2147483643
记住Java中没有无符号左移。因为左移操作符 <<
总是保持符号位,即使在移动过程中可能会导致符号位变为1。如果需要进行无符号左移操作,可以使用无符号右移操作 >>>
来实现。因为在大多数情况下,无符号左移和无符号右移的效果是一样的,都是向左或向右移动指定位数,只是填充的位不同。
七、Java中的其它特殊运算符
7.1 &
按位与运算 &
是双目运算符。其功能是参与运算的两数各对应的二进制位相与
。只有对应的两个二进位均为1时,结果位才为1 ,否则为0。参与运算的数以补码方式出现。
使用场景:
- 位操作: 位运算符在底层数据处理和优化中很常见,例如对数据进行位掩码、位清除、位设置等操作。
- 权限控制: 在权限控制中,可以使用位运算来对不同权限进行组合或判断。
- 位集合: 将每个位视为一个标志,可以通过按位与运算来检查是否设置了特定的标志位。
例如,如果有一个整数 a 表示某个权限的掩码,另一个整数 b 表示用户的权限,通过 a & b
运算可以判断用户是否具有某个权限。
7.2 |
按位或运算 |
是双目运算符。其功能是参与运算的两数各对应的二进制位相或。只要两数的二进制位中有一个是1,那么按位或的结果就是1;只有二进制两位都为0时,结果为0。
使用场景:
- 位操作: 位运算符在底层数据处理和优化中很常见,例如对数据进行位标记、位组合等操作。
- 权限控制: 在权限控制中,可以使用位运算来对不同权限进行组合或判断。
- 位集合: 将每个位视为一个标志,可以通过按位或运算来设置特定的标志位。
例如,如果有一个整数 a 表示某个权限的掩码,另一个整数 b 表示用户的权限,通过 a | b
运算可以将用户的权限添加到掩码中。
7.3 ^
按位异或运算 ~
是双目运算符。其功能是参与运算的两数各对应的二进制位相异或。两个数字的二进制位相同则取0,不同值则取1。
异或运算符具有以下的性质:
(1)交换律: (2)结合律:
(a^b)^c == a^(b^c)
(3)对于任何数X,都有:
x^x=0,x^0=x
(4)自反性
A XOR B XOR B = A xor 0 = A
使用场景:
异或运算最常见于多项式除法,不过它最重要的性质还是自反性:A XOR B XOR B = A
,即对给定的数A,用同样的运算因子(B)作两次异或运算后仍得到A本身。这是一个神奇的性质,利用这个性质,可以获得许多有趣的应用。
例如,大部分程序教科书都会向初学者指出,要交换两个变量的值,必须要引入一个中间变量。但如果使用异或,就可以节约一个变量的存储空间: 设有A,B两个变量,存储的值分别为a,b,则以下三行表达式将互换他们的值 表达式 (值) :
A=A XOR B (a XOR b)
B=B XOR A (b XOR a XOR b = a)
A=A XOR B (a XOR b XOR a = b)
public class MyTestMain { public static void main(String[] args) { int a = 5; int b = 6; a = a^b; b = a^b; a = a^b; System.out.println("a:" + a); System.out.println("b:" + b); } }
输出结果:
a:6
b:5
google的一个面试题:
一个数组存放若干整数,一个数出现奇数次,其余数均出现偶数次,找出这个出现奇数次的数?
上面的问题可以使用按位异或运算巧妙解决:
public class MyTestMain { public static void main(String[] args) { int[] nums = {2, 2, 3, 3, 4, 4, 5}; int returnValue = 0; for (int num : nums) { returnValue ^= num; } System.out.println(returnValue); } }
输出结果:
5