Java 位运算符和移位运算符

参考:

Bitwise and Bit Shift Operators

《Java 编程思想 第3章 操作符》


今天学习 Java BitSet 类时,发现对于位运算符和移位运算符的操作有些陌生,所以重新复习一下


主要内容:

  1. 位操作浅析
  2. 位运算符
  3. 移位运算符
  4. 优先级
  5. 问题解析
  6. 取值范围

位操作浅析

Java 可在整数类型(integral type)数据上进行位(bit)操作

整数类型:

  • 字节型(byte8 位)
  • 短整型(short16 位)
  • 整型(int32 位)
  • 长整型(long64 位)

原码,反码和补码

参考:

原码,反码和补码的关系?

原码、反码、补码的产生、应用以及优缺点有哪些?

首先原码,反码和补码都是基于二进制数进行的

对于正数而言,其原码,反码和补码一致(无符号数就是正数

Java 中的整数类型都是有符号数,即其最高位为符号位(正数为 0,负数为 1

默认情况下,二进制数就是原码表示,所以将十进制数 15 转换为二进制原码就是(假定为 8 位整数)

00001111

将十进制数 -15 转换为二进制原码就是

10001111

原码和反码相互转换规则:负数保留符号位不变,其它位按位取反

将十进制 -15 转换为二进制反码就是

11110000

原码和补码相互转换规则:负数符号位不变,其余位求反再加 1

将十进制 -15 转换为二进制补码就是

11110001

加减运算

在计算机中,使用补码保存整数类型数据因为补码格式有利于计算机进行移位运算

加减运算时,补码直接相加即可(符号位参与运算

进制转换

Java 不能直接表示二进制整数,但可以表示成八进制(以数字 0 开头),十进制(没有前置)和十六进制(以数字 0 和 字符 x 开头),默认情况使用十进制计算

比如对于整数 15 来说,其八进制表示为 017,十六进制表示为 0xf

通常情况下使用 十六进制 来表示 二进制

一个整型数据占 4 个字节,共 32 位,那么对于整型数 a = 15 来说,其二进制表示如下

// 前面共 28 个 0
0000...0001111

转换为十六进制,就是 0x0000000f


位运算符

Java 提供了 4 种位运算符

  • 位与运算符(bitwise and operator):&
  • 位或运算符(bitwise inclusive or operator):|
  • 位异或运算符(bitwise exclusive or operator):^
  • 位取反运算符(bitwise invert operator):~

这些运算符是在二进制补码上进行操作

测试程序如下:

public static void main(String[] args) {
    byte a = 15;
    byte b = -15;

    System.out.println(a & b);
    System.out.println(a | b);
    System.out.println(a ^ b);
    System.out.println(~a);
    System.out.println(~b);
}

这里写图片描述

一个字节数占 8 位,将 ab 转换为二进制:

a = 0000 1111
b = 1111 0001

Note:计算机使用补码表示

位与运算符:仅当两个操作数同一下标的值均为 1 时,结果才为 1

a & b = 0000 1111 & 1111 0001 = 0000 0001(补) = 0000 0001(原) = 1

位或运算符:只要两个操作数同一下标的值有一个为 1 时,结果就为 1

a | b = 0000 1111 & 1111 0001 = 1111 1111(补) = 1000 0001(原) = -1

位异或运算符:只有两个操作数同意下标的值不相等时,结果才为 1

a ^ b = 0000 1111 ^ 1111 0001 = 1111 1110(补) = 1000 0010(原) = -2

位取反运算符:按位取反每一位

~a = ~0000 1111 = 1111 0000(补) = 1001 0000(原) = -16
~b = ~1111 0001 = 0000 1110(补) = 0000 1110(原) = 14

Note 1:byte 或者 short 类型数值进行位运算后,返回的是 int 类型数值(没有找到资料说明在位运算之前是否已经进行了转换,不过先将 ab 转换为 int 类型二进制再进行计算的结果和上面一致)

Note 2:位运算符的操作不排除符号位


移位运算符

Java 提供了 3 种移位运算符

  • 左移运算符(left shift operator):<<
  • 右移运算符(right shift operator):>>
  • 无符号右移运算符(unsigned right shift operator):>>>

示例程序如下:

public static void main(String[] args) {
    System.out.println("正数移位");
    compute(15);
    System.out.println("负数移位");
    compute(-15);
}

public static void compute(int a) {
    println(a << 3);
    println(a << -61);
    println(a << 35);

    println(a >> 3);
    println(a >> -61);
    println(a >> 35);

    println(a >>> 3);
    println(a >>> -61);
    println(a >>> 35);
}

public static void println(int n) {
    System.out.println(n);
}

对于移位运算符而言,左侧操作数表示要移动的二进制数,右侧操作数表示要移动的位数

进行移位操作时,需要注意以下几点:

  • 对于 byte 或者 short 类型数值,进行移位操作时,会先转换为 int 类型,然后进行移位(如果是 long 类型,则不变

  • 对于右侧操作数而言,在进行移位之前,先转换为二进制数(补码)。如果左侧数是 int 类型,则取右侧操作数最右端 5 位数值进行移动;如果是 long 类型数值,则取右侧操作数最右端 6 位数值进行移动

左移运算符:数值位向左移动指定位数

15 << 3 = 0x0000000f << 3 = 0x00000078(补,原) = 120
15 << -61 = 0x0000000f << 0xffffffc3(左侧是 int 类型,取右侧 5 位) = 0x0000000f << 3 = 0x00000078(补,原) = 120
15 << 35 = 0x0000000f << 0x00000023(左侧是 int 类型,取右侧 5 位) = 0x0000000f << 3 = 0x00000078(补,原) = 120

-15 << 3 = 0xfffffff1 << 3 = 0xffffff88(补) = 0x80000078(原) = -120
-15 << -61 = 0xfffffff1 << 0xffffffc3(左侧是 int 类型,取右侧 5 位) = 0xfffffff1 << 3 = 0xffffff88(补) = 0x80000078(原) = -120
-15 << 35 = 0xfffffff1 << 0x00000023(左侧是 int 类型,取右侧 5 位) = 0xfffffff1 << 3 = 0xffffff88(补) = 0x80000078(原) = -120

右移运算符:数字位向右移动指定位数(如果左操作数是正数,高位补 0 ;如果是负数,高位补 1

15 >> 3 = 0x0000000f >> 3 = 0x00000001 = 1
-15 >> 3 = 0xfffffff1 >> 3 = 0xfffffffe(补) = 0x80000002(原) = -2

无符号右移运算符:功能和右移运算符一样,不过无论正负,高位均补 0

15 >>> 3 = 0x0000000f >>> 3 = 0x00000001 = 1
-15 >> 3 = 0xfffffff1 >>> 3 = 0x1ffffffe(补,原) = 2^29 - 2 = 536870910

Note 1:移位运算时,从符号位开始操作

Note 2:由结果可知,左移一位相当于乘以2,右移一位相当于除以 2


优先级

参考:运算符优先级

Java 运算符优先级如下图所示:

这里写图片描述

由图中可知,位运算符和移位运算符的优先级从左到右如下:

~,<<,>>,>>>,&,^,|


问题解析

之前学习类 BitSet 时遇到了很多的位运算,但是有一些操作没搞明白,下面是我总结的一些问题和解答

  • 问题一:移动位数超过其精度如何解决

    • 解答:在进行移位操作之前,先将右侧数值转换成二进制(其实在计算机内部就是以二进制补码保存的)。如果左侧操作数为 int 类型数值,那么取右侧操作数的最右端 5 位进行移位;或者左侧操作数是 long 类型数值,取右侧操作数的最右端 6 位进行移位
  • 问题二:移动位数为负如何解决

    • 解答:和问题一的解答一样,取右侧操作数的最右端 5/6 位进行移位
  • 问题三:如何解决符号位的问题

    • 解答:计算机以二进制补码形式保存整型数值。
      • 无论是加减 / 位运算 / 移位运算,符号位均参与其中
  • 问题四:已知起始下标 fromIndex 和 终止下标 toIndex,如何在位集中设定这一段连续区间为 true

    • 解答:以 int 类型为例,位集长度为 32 位,假设 fromIndex = 3toIndex = 10,那么示例程序如下:

      public static final int WORD_MASK = 0xffffffff;
      
      public static void main(String[] args) {
          int fromIndex = 3;
          int toIndex = 10;
      
          int firstWordMask = WORD_MASK << fromIndex;
          int lastWordMask = WORD_MASK >>> -toIndex;
          int res = (firstWordMask & lastWordMask);
          System.out.println(Integer.toBinaryString(firstWordMask));
          System.out.println(Integer.toBinaryString(lastWordMask));
          System.out.println(Integer.toBinaryString(res));
      }
      

      这里写图片描述

      要设定位集中连续区间位值为 true,可以设定一个辅助常量 WORD_MASK,保证每个位均为 true

      定义起始下标 fromIndex = 3,结束下标 toIndex = 10

      计算 firstWordMask,使得 WORD_MASK 向左移动 fromIndex 个位置,低位补 0,结果使得区间 [0-fromIndex) 的位值为 0

      计算 lastWordMask,使得 WORD_MASK 向右移动 n 个位置,高位补 0,结果使得区间 (toIndex-32] 的位值为 0

      最后进行位与操作,得到区间 [fromIndex-toIndex] 的位值为 true

      Note:对于 int 值而言,设 a = 3,则取 -3 的后 5 位就是 (32-3)=29;若是 long 值,取 -3 的后 6 位就是 (64-3)=61


取值范围

字节类型占 8 位,其中最高位为符号位,所以其取值范围为 [-2^7-1,2^7-1] = [-127,127],其中 0 有两种表示方式

00000000 或者 10000000

10000000 当作 -128,则 字节类型的取值为 [-128,127]

public static final byte   MIN_VALUE = -128;
public static final byte   MAX_VALUE = 127;

示例程序如下:

public static void main(String[] args) {
    System.out.println(Integer.toBinaryString(Byte.toUnsignedInt(Byte.MAX_VALUE)));
    System.out.println(Integer.toBinaryString(Byte.toUnsignedInt(Byte.MIN_VALUE)));
}

这里写图片描述

同理,短整型的取值范围为 [-2^15,2^15-1],整型的取值范围为 [-2^31,2^31-1],长整型的取值范围为 [-2^63,2^63-1]

// Short.java
public static final short   MIN_VALUE = -32768;
public static final short   MAX_VALUE = 32767;

// Integer.java
@Native public static final int   MIN_VALUE = 0x80000000;
@Native public static final int   MAX_VALUE = 0x7fffffff;

// Long.java
@Native public static final long MIN_VALUE = 0x8000000000000000L;
@Native public static final long MAX_VALUE = 0x7fffffffffffffffL;

示例程序如下:

public static void main(String[] args) {
    System.out.println(Integer.toBinaryString(Short.toUnsignedInt(Short.MAX_VALUE)));
    System.out.println(Integer.toBinaryString(Short.toUnsignedInt(Short.MIN_VALUE)));

    System.out.println(Integer.toBinaryString(Integer.MAX_VALUE));
    System.out.println(Integer.toBinaryString(Integer.MIN_VALUE));

    System.out.println(Long.toBinaryString(Long.MAX_VALUE));
    System.out.println(Long.toBinaryString(Long.MIN_VALUE));
}

这里写图片描述

猜你喜欢

转载自blog.csdn.net/u012005313/article/details/78543809