一分钟看明白Java位运算

453 阅读6分钟

前言

在阅读源码的时候,经常会碰到位运算,例如Java8中的HashMap部分源码。不同语言有各自的位运算方式,又大同小异。本篇文章带你一分钟彻底掌握Java中的位运算

    /**
     * 这段代码是计算hashcode的,其中的位运算和异或运算是为了降低hash碰撞概率
     */
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

正文


1. 整数的机器级表示

对于Java,int类型长度为32位。我这里为了方便说明,假设某种语言的整数类型位4位,那么能表示的的范围是多少?

  • 如果是无符号类型,最大位二进制0~1111,也就是0~15
  • 如果有符号,最高位符号位1代表负数,0代表正数,因此能表示的范围为二进制1000~0111,也就是-8~7

为什么二进制1000等于-8?

这就涉及到补码编码,以下是其定义

也就是说,对于4位整数1000,最高位为1符号位,说明是一个负数。那么它的计算方式是:

  • -1 * 2^3 + 0 * 2^2 + 0 * 2^1 + 0 * 2^0=-8+0+0+0=-8

再例如二进制的1111,按照这个计算方式结果是:

  • -1 * 2^3 + 1 * 2^2 + 1 * 2^1 + 1 * 2^0=-8+4+2+1=-1

2. Java的位移运算

了解整数在机器中表示,就很容易能明白位移运算了。这里我拿Java中的int类型演示

int类型占32位,因此能表示的范围为-2^31 ~ 2^31-1,为了便于阅读,我这里把这个范围的十进制和二进制打印出来

可以看到

  • int类型的十进制范围表示为:-2147483648~2147483647

  • int类型的二进制表示范围为: 10000000 00000000 00000000 00000000 ~ 01111111 11111111 11111111 11111111

public class BitShift {
    public static void main(String[] args) {
        System.out.println(Integer.MAX_VALUE);  //2147483647
        System.out.println(Integer.MIN_VALUE);  //-2147483648
        System.out.println(Integer.toBinaryString(Integer.MAX_VALUE));  // 01111111 11111111 11111111 11111111
        System.out.println(Integer.toBinaryString(Integer.MIN_VALUE));  // 10000000 00000000 00000000 00000000
    }
}

左移<<

二进制数向左移动k位,丢弃最高的k位,并在有右边补k个0

  • 01111111 11111111 11111111 11111111左移一位,符号位变成1,低位用0填充,所以结果位11111111 11111111 11111111 11111110,通过补码编码得出结果为-2
  • 10000000 00000000 00000000 00000000左移一位,符号位变为0,代表正数,低位同样用0填充,结果位00000000 00000000 00000000 00000000,因此结果为0
System.out.println(Integer.MAX_VALUE<<1);   //-2
System.out.println(Integer.toBinaryString(-2)); //11111111 11111111 11111111 11111110
------------------------------------------------
System.out.println(Integer.MIN_VALUE<<1);   //0

算术右移>>

算术右移到方式比较微妙,二进制右移动k位,丢弃低k位,并在高k位补最高位的值。其目的是为了负数的运算

如下:算术右移动后,高位原本是几就用几补充

  • 01111111 11111111 11111111 11111111算术右移1位为00111111 11111111 11111111 11111111
  • 10000000 00000000 00000000 00000000算术右移1位为11000000 00000000 00000000 00000000

可以看到十进制无论是正还是负数,逻辑右移一位相当于除以二

System.out.println(Integer.MAX_VALUE>>1);   //  1073741823
System.out.println(Integer.MIN_VALUE>>1);   //  -1073741824

System.out.println(Integer.toBinaryString(Integer.MAX_VALUE>>1));   //  00111111 11111111 11111111 11111111
System.out.println(Integer.toBinaryString(Integer.MIN_VALUE>>1));   //  11000000 00000000 00000000 00000000

逻辑右移>>>

逻辑右移就很简单了,直接高k位补0,丢弃低k位

System.out.println(Integer.MAX_VALUE>>>1);   //  1073741823
System.out.println(Integer.MIN_VALUE>>>1);   //  1073741824

System.out.println(Integer.toBinaryString(Integer.MAX_VALUE>>>1));   //  00111111 11111111 11111111 11111111
System.out.println(Integer.toBinaryString(Integer.MIN_VALUE>>>1));   //  01000000 00000000 00000000 00000000

3. C语言中的位运算

  • 对于无符号整数,由移必须是逻辑的,也就是高位填充0

  • 对于有符号整数,有些机器会进行算数右移,有些机器会逻辑右移动

以下为8位整数的位移情况

补充

一个数左移动或者右移动k位,k非常大会是什么情况?

可以看到,对于Java int类型,右移或者左移32位相当于没移动,位移33位相当于位移1位。这是因为位运算会先将k取模再进行位移。比如k=33%32=1

System.out.println(Integer.MAX_VALUE);  //2147483647
System.out.println(Integer.MAX_VALUE>>32);  //2147483647

System.out.println(Integer.MAX_VALUE>>33);  //1073741823
System.out.println(Integer.MAX_VALUE>>1);   //1073741823

System.out.println(Integer.MAX_VALUE<<32);  //2147483647
System.out.println(Integer.MAX_VALUE>>>32); //2147483647

最后附上完整测试代码

public class BitShift {
    public static void main(String[] args) {
        System.out.println(Integer.MAX_VALUE);  //2147483647
        System.out.println(Integer.MIN_VALUE);  //-2147483648
        System.out.println(Integer.toBinaryString(Integer.MAX_VALUE));  // 01111111 11111111 11111111 11111111
        System.out.println(Integer.toBinaryString(Integer.MIN_VALUE));  // 10000000 00000000 00000000 00000000

        System.out.println(Integer.MAX_VALUE<<1);   //-2
        System.out.println(Integer.MIN_VALUE<<1);   //0
        System.out.println(Integer.toBinaryString(-2)); //11111111 11111111 11111111 11111110

        System.out.println(Integer.MAX_VALUE>>1);   //  1073741823
        System.out.println(Integer.MIN_VALUE>>1);   //  -1073741824

        System.out.println(Integer.toBinaryString(Integer.MAX_VALUE>>1));   //  00111111 11111111 11111111 11111111
        System.out.println(Integer.toBinaryString(Integer.MIN_VALUE>>1));   //  11000000 00000000 00000000 00000000

        System.out.println(Integer.MAX_VALUE>>>1);   //  1073741823
        System.out.println(Integer.MIN_VALUE>>>1);   //  1073741824

        System.out.println(Integer.toBinaryString(Integer.MAX_VALUE>>>1));   //  00111111 11111111 11111111 11111111
        System.out.println(Integer.toBinaryString(Integer.MIN_VALUE>>>1));   //  01000000 00000000 00000000 00000000


        System.out.println(Integer.MAX_VALUE);  //2147483647
        System.out.println(Integer.MAX_VALUE>>32);  //2147483647

        System.out.println(Integer.MAX_VALUE>>33);  //1073741823
        System.out.println(Integer.MAX_VALUE>>1);   //1073741823

        System.out.println(Integer.MAX_VALUE<<32);  //2147483647
        System.out.println(Integer.MAX_VALUE>>>32); //2147483647
    }
}