likes
comments
collection
share

Java特殊运算符及其应用

作者站长头像
站长
· 阅读数 18

Java特殊运算符及其应用

一、前言

当涉及位操作和位级运算时,Java 提供了一组特殊的运算符,即左移(<<)和右移(>>)运算符。这些运算符与普通的算术和逻辑运算符不同,它们操作的是数字的二进制位。位操作是计算机底层编程中常用的技巧,能够高效地执行一些操作,如数值乘除以2的幂次方,处理标志位,以及在一些位级编码场景下进行数据处理。

左移和右移运算符用于将二进制位向左或向右移动指定的位数,从而实现对数值的快速操作。左移操作将数值的二进制表示向左移动,等效于乘以2的幂次方,而右移操作将数值的二进制表示向右移动,等效于除以2的幂次方。这些运算符在处理大数据集、位掩码、网络协议以及嵌入式系统等领域都具有广泛的应用。

然而,需要注意的是,位操作并不常见于一般的业务逻辑编程,而更多地出现在需要对二进制数据进行处理的底层系统级编程中。在使用位操作时,应该清晰地理解位操作的语义,防止出现意外错误。同时,要考虑到可读性和代码维护性,不应滥用位操作,而是在合适的场景下使用,以提高代码的效率和性能。

二、Java的左移、右移运算符

  • << 是左移;如:<<1就是左移一位;<<2就是左移两位
  • >>”是右移;如:>>1就是右移一位;>>2就是右移两位

记忆技巧: 尖括号的方向朝向哪就是哪移。

左移就是将目标转换为二进制后,从右向左移动一位,右边补零,左边移除。 Java特殊运算符及其应用

右移就是将目标转换为二进制后,从左向右移动一位,左边补零,右边移除。。 Java特殊运算符及其应用

三、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的幂次方的操作。以下是一些使用左移右移的好处:

  1. 速度优势: 左移和右移操作是位级操作,它们在底层直接对二进制数据进行移动,相比于使用乘除法运算符,位移操作可以更快地完成数值的变换。这在一些需要高效计算的场景下尤为重要。
  2. 数值操作: 左移和右移可以用于实现数值的倍增或减少。例如,将一个数值左移n位,等效于将其乘以2的n次方;将一个数值右移n位,等效于将其除以2的n次方。
  3. 位掩码操作: 在位掩码操作中,可以使用左移来设置位,使用右移来读取位。这在一些标志位、开关状态的处理中非常有用。
  4. 位级编码: 在某些编码和解码场景下,需要将信息编码到特定的位上,或者从特定位上解码信息。左移和右移操作可以用于实现这种位级的数据处理。
  5. 嵌入式系统: 在嵌入式系统中,资源有限,效率至关重要。位移操作可以提供更紧凑和高效的代码,减少资源消耗。

4.2 弊端:

  1. 可读性下降: 左移和右移操作对于非熟悉位操作的开发者来说可能不太直观,代码的可读性可能会下降,使得代码难以理解和维护。
  2. 位移越界: 在使用右移操作时,如果对有符号的整数进行右移,可能会导致符号位扩展,从而改变数值的意义。例如,对于负数的右移可能会导致意外的结果。
  3. 移位溢出: 如果使用左移操作将某个位移到超出数据类型的范围,就会发生移位溢出,导致结果不正确。
  4. 代码依赖于底层实现: 使用位操作可能会使代码与底层硬件或编译器的实现相关,导致代码的可移植性下降。
  5. 复杂逻辑错误: 如果没有正确地处理边界情况,可能会导致复杂的逻辑错误,尤其是在涉及位级运算的复杂算法中。
  6. 难以维护: 过度使用位操作可能使代码变得难以理解和维护,特别是在逻辑复杂的情况下。
  7. 代码不易调试: 位操作可能会导致一些难以调试的问题,因为它们涉及底层位级表示,而不是更常见的数值操作。

五、使用左移、右移注意事项

  1. 带符号左移需要考虑移动后数字变为负数的情况,因为二进制的首位为符号位。
  2. 从上述中我们知道,左移就是将一个数字的二进制编码向左边移动,右边补零,右移相反。如果一个int类型的数字向左移32位结果是不是就是0呢?答案是错误的。当int类型数字进行左移,并且左移位数大于等于32位时,会先与32求余,然再进行移动。32%32 = 0,使用int类型向左移32位相当于不移动,byteshort类型同理。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位(不移位)
    }
}
  1. double和float不能使用位移来操作,编译不通过。 Java特殊运算符及其应用

六、无符号右移>>>

无符号右移和右移原理一样,只不过是右移后在左边补零后舍弃掉符号位。如下所示:

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