位运算

位运算

位运算是计算机中的基础运算单元,Java为了提高程序的运行性能,便可直接通过位运算符进行计算操作。

Java中提供的位运算符由擦欧总书和位运算符所组成,可以实现对数值类型的二进制数进行运算(在Java中所有数据都是以二进制数据的****形式进行运算的),在位运算中提供有两类运算符:逻辑运算符(~、&、|、^)移位运算符(>>、<<、>>>),如下所示:

逻辑运算符描述
&按位“与”
|按位”或“
^异或(相同为0,不同为1)
~取反
移位运算符描述
<<左移位
>>右移位
>>>无符号右移位

逻辑运算符可与等号“=”联合使用,以便于更加简便地运算和赋值,即**“&=”、“|=”、“^=”都是合法的**(但由于**“~”是一元操作符**,所以不能与等号“=”连用)。

移位运算符均可与等号“=”联合使用,即**“<<=”、“>>=”、“>>>=”都是合法的**。此时,操作数左边的值会移动由右边数值指定的位数,再将得到的结果赋值给左边的变量。


1、十进制转化为二级制

十进制数据转化为二进制数据的原则为数据除以2取余数,最后倒着排列,例如,25的二进制值为11001,如下:

  • 25/2 = 12——余数1
  • 12/2 = 6 ——余数0
  • 6 /2 = 3 ——余数0
  • 3 /2 = 1 ——余数1
  • 1 /2 = 0 ——余数1

倒着排列余数便可得11001。但是值得注意的是,Java的int型数据为32位(4字节),所以实际最终的数据为00000000 00000000 00000000 0011001

范例:

public class TestDemo {
    public static void main(String[] args) {
        int x = 13;
        int y = 7;
        System.out.println(x & y);  // 5
        System.out.println(x | y);  // 15
        System.out.println("x左移位后的计算结果:" + (x << 2));   // 52
        System.out.println("原变量x执行移位后的结果:" + x);      // 13 由此可得,移位后原来的值不变
    }
}
/*
结果分析:
13 的二进制:00000000 00000000 00000000 00001101  
7 的二进制:00000000 00000000 00000000 00000111   
& 的结果:00000000 00000000 00000000 00000101       转换为十进制:5
| 的结果:00000000 00000000 00000000 00001111       转换为十进制:15
<< 2 的结果:00000000 00000000 00000000 00110100    转换为十进制:52
*/

2、右移位(>>)与无符号右移位(>>>)的区别

在计算机中,数值表示正负是通过+、-来表示的:0–正,1–负

  1. 有符号右移 >>(正数,高位补0;负数,高位补1)
  • 正数:4 >> 2
    4 的二进制数(原码、补码、反码都相同):00000000 00000000 00000000 00000100

    4 >> 2(高位补0):00000000 00000000 00000000 00000001 转化为十进制:1

    tips:x >> n 的结果就是 【x / (2^n)】

  • 负数:-4 >> 2(高位补1)

    -4 的 原码:10000000 00000000 00000000 00000100(负数–>最高位为1

    -4 的 补码:11111111 11111111 11111111 11111100

    负数原码 --> 补码:保证符号位不变,其余位置取反加1(或者 从右往左第一个1右边不变,左边全部取反)

    -4 >> 2(高位补1):11111111 11111111 11111111 1111111

    转化为原码(保留符号位,按位取反加上1):10000000 00000000 00000000 00000001 转化为十进制:-1

  1. 无符号右移 >>> (不论正负,高位均补0)
  • 正数:4 >>> 2

    同 4 >> 2 的运算相同,结果为:1

  • 负数:-4 >>> 2
    -4 的 原码:10000000 00000000 00000000 00000100(负数–>最高位为1)

    -4 的 补码:11111111 11111111 11111111 11111100

    -4 >>> 2(高位补0): 00111111 11111111 11111111 11111111 转化为十进制:1073741823

public class TestDemo {
    public static void main(String[] args) {
        int x = 4;
        int y = -4;
        
        System.out.println(x + "<< 2 : " + (x << 2));       // 16
        System.out.println(x + ">>> 2 : " + (x >>> 2));     // 1
        System.out.println(x + ">> 2 : " + (x >> 2));       // 1
        System.out.println(y + ">>> 2 : " + (y >>> 2));     // 1073741823
    }
}

3、移位运算符的一些疑难点

3.1 对于“取移动位数(二进制形式)右端的低5位作为实际移动位数”

《Java编程思想》这本书中提到:如果对char、byte 或者 short类型的数值进行移位处理,那么在移位进行之前,它们会被转换为int类型,并且得到的结果也是一个int类型。只有数值右端的低5位才有用。这样可以防止我们移位超过int型值所具有的位数。(译注:因为2的5次方为32,而int型值只有32位)若对一个long类型的数值进行处理,最后得到的结果也是long。此时只会用到数值右端的低6位,以防止移位超过long型数值具有的位数。

对于上面这段话,我个人的理解是:移位运算符的运算对象是基本数据类型中的单个“比特”(bit),即二进制数。而int类型是32位(2^5=32),若移位32位以上,那么原来的数会丢失其信息,显然没有什么意义。故移位运算符右端的数(转换为二进制)的低5位才有用。而对于long类型也是同理。例如,对于一个int型,x << y:

  • 若 y < 32,移位后的结果显然在意料之内

  • 若 y > 32,超过了int类型所能表示的范围,此时,先将y转换为二进制数并取该数的低5位(此低5位转换的十进制数就是要对x移动

    的位数)

public class TestDemo {
    public static void main(String[] args) {
        int i = 2;
        
        System.out.println("i的二进制数:" + Integer.toBinaryString(i));
        System.out.println("i的十进制数:" + i);

        System.out.println("i << 31 二进制数:" + Integer.toBinaryString(i << 31));
        System.out.println("i << 31 十进制数:" + (i << 31));

        System.out.println("i << 32 二进制数:" + Integer.toBinaryString(i << 32));
        System.out.println("i << 32 十进制数:" + (i << 32));

        System.out.println("i >> 33 二进制数:" + Integer.toBinaryString(i << 33));
        System.out.println("i >> 33 十进制数:" + (i << 33));
    }
}
/*
输出结果:
i的二进制数:10
i的十进制数:2

i << 31 二进制数:0
i << 31 十进制数:0

i << 32 二进制数:10
i << 32 十进制数:2

i >> 33 二进制数:100
i >> 33 十进制数:4
*/

上述例子,i << 33,对i左移33位时,先将i转换为二进制数:00000000 00000000 00000000 00000010,33的二进制数:00000000 00000000 00000000 00100001(取其右端低5位,即00001,转换为十进制数为1),故等价为i << 1,实际上是对i左移1位。

tips:被移位的int类型数值为x,x >> n(或x << n),则实际移动的位数为:n % 32。

3.2 “无符号”右移位操作符结合赋值操作对byte、short类型数值的移位运算

《Java编程思想》这本书中提到:“移位”可与“等号”(<<=或>>=或>>>=)组合使用。此时,操作符左边的值会移动由右边的值指定的位数,再将得到的结果赋给左边的变量。但在进行“无符号”右移位结合赋值操作时,可能会遇到一个问题:如果对byte或short值进行这样的移位运算,得到的可能不是正确的结果。它们会先被转换为int类型,再进行右移操作,然后被截断,赋值给原来的类型,在这种情况下可能得到-1的结果。

对于上面这段话,我个人的理解是(注:byte类型的分析同short类型):

  1. short类型的>>>操作:

    • short类型值为-1,转换为二进制数:11111111 11111111

    • Integer.toBinaryString(s1)会把s1转为int类型,故打印结果为:11111111 11111111 11111111 11111111

    • Integer.toBinaryString(s1 >>> 10),根据原文可知,会将short类型的s2转换为int类型,再无符号右移10位

    • 移位后得到:00000000 00111111 11111111 11111111

    • 然后直接返回int类型,故打印结果为:111111 11111111 11111111

  2. short类型的>>>=操作:

    • short类型值为-1,转换为二进制数:11111111 11111111

    • Integer.toBinaryString(s2)会把s1转为int类型,故打印结果为:11111111 11111111 11111111 11111111

    • 执行s2 >>>= 10,根据原文可知,会将short类型的s2转换为int类型,再无符号右移10位

    • 移位后得到:00000000 00111111 11111111 1111111(之后再截断出后16位赋给s2,得:11111111 11111111)

    • 打印的时short类型的s2又被转换为int类型,故打印结果为:11111111 11111111 11111111 11111111

public class TestDemo {
    public static void main(String[] args) {
        short s1 = -1;
        // 11111111 11111111 11111111 11111111
        System.out.println("s1: " + Integer.toBinaryString(s1));  
        
         // 111111 11111111 11111111
        System.out.println("s1 >>> 10: " + Integer.toBinaryString(s1 >>> 10)); 

        short s2 = -1;
        // 11111111 11111111 11111111 11111111
        System.out.println("s2: " + Integer.toBinaryString(s2));    
        
        s2 >>>= 10;
        // 11111111 11111111 11111111 11111111
        System.out.println("s2 >>>= 10: " + Integer.toBinaryString(s2));    
        
        // 11111111 11111111 11111111 11111111
        //System.out.println("s2 >>>= 10: " + Integer.toBinaryString(s2 >>>= 10));
    }
}
/*
输出结果:
s1: 11111111111111111111111111111111
s1 >>> 10: 1111111111111111111111
s2: 11111111111111111111111111111111
s2 >>>= 10: 11111111111111111111111111111111
*/

tips:简而言之,short、byte类型在进行“>>>=”操作时,需截断赋值。那么移动几位才不出现此现象呢?答案便是:大于等于17位。

以“short a = -1; a >>>= 17”为例: 则有

11111111 11111111 11111111 11111111 ——> 00000000 00000000 01111111 11111111

再将运算得到的int类型的结果赋值给short类型的变量a

00000000 00000000 01111111 11111111 ——> 01111111 11111111

转换为十进制数为,即a = 32767。

public class TestDemo {
    public static void main(String[] args) {
        short b = -1;
        System.out.println("b的二进制数: " + Integer.toBinaryString(b));
        System.out.println("b >>>= 16: " + Integer.toBinaryString(b >>>= 16));
        System.out.println("b的十进制数: " + b);

        short a = -1;
        System.out.println("a的二进制数: " + Integer.toBinaryString(a));
        System.out.println("a >>>= 17: " + Integer.toBinaryString(a >>>= 17));
        System.out.println("a的十进制数: " + a);

        short c = -1;
        System.out.println("a的二进制数: " + Integer.toBinaryString(a));
        System.out.println("c >>>= 17: " + Integer.toBinaryString(c >>>= 18));
        System.out.println("c的十进制数: " + c);
    }
}

/*
输出结果:
b的二进制数: 11111111111111111111111111111111
b >>>= 16: 11111111111111111111111111111111
b的十进制数: -1

a的二进制数: 11111111111111111111111111111111
a >>>= 17: 111111111111111
a的十进制数: 32767

a的二进制数: 111111111111111
c >>>= 17: 11111111111111
c的十进制数: 16383
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

窝在角落里学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值