Java位运算

Posted by wangtiegang on September 15, 2019

Java位运算包括按位操作符和移位操作符,通常用的不多,但是有时候我们会在源码中接触到,合理利用可以提高运算速度。在学习位运算之前,我们需要先回忆下原码,反码,补码。

原码、反码、补码

  • 原码

二进制中对数字的一种表示方法,最高位为符号位,正数该位为0,负数该位为1,其他位表示数值的大小。例如用一字节来存储十进制1,原码为00000001,-1为10000001。

原码的特点是表示简单直观,与真值转换方便,但不适合运算,比如 1 + (-1)= 0,用原码表示则为 00000001+10000001=10000010 ,换成十进制为 -2 ,结果错了。

  • 反码

反码在原码的基础上推算而来,符号位不变,数值为取反,0变1,1变0。反码是为了由原码计算补码方便。1的反码为:01111111。

  • 补码

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

正数的补码与原码一样,不需要计算。 负数的补码为反码再加上1。

总结下就是:

原码的数值位 与真值的数值位相同,符号位正数为0,负数为1; 反码的符号位不变,数值位在原码的基础上取反; 补码正数与原码相同,负数的数值位为反码的数值位加1,左边溢出的位丢弃。 计算机中通常是使用补码的形式来表示和存储一个数

举例:

十进制 原码 反码 补码
+0 00000000 01111111 00000000(正数跟原码一致)
-0 10000000 11111111 00000000(加1后,最高位1溢出,丢弃)

位运算

位运算是针对二进制的每一位进行运算,它是专门针对数字0和1进行的操作。程序中的所有数在计算机内存中都是以二进制的补码形式储存的。位运算既可以节约内存,同时使程序速度更快效率更高。

Java中可以进行位运算的类型有 long, int, short, byte, char;但在实际运算中, byte、short、char先转换为长度为32位的 int 类型,然后进行位运算的, long长度为64位可以直接进行位运算,所以 int 和 long 是可以直接进行位运算的。

按位操作符

&(与运算) 只要有一个为0,就为0,同时为1则为1,如 0101 & 0100 结果为 0100
|(或运算) 只要有一个为1,就为1,同时为0则为0,如 0101 | 0100 结果为 0101
~(取反运算) 0变1,1变0,如 ~0101 结果为 1010
^(异或运算) 不同为1,相同为0,如 0101 ^ 0100 结果为 0001

移位操作符

<<(左移) 符号位不变,丢弃最高位,0补最低位 ,如 01010000 << 2 结果为 01000000
>>(右移) 符号位不变,左边补上符号位,如 01010000 >> 2 结果为 00010100
>>>(无符号右移) 忽略了符号位扩展,0补最高位,如 01010000 >>> 2 结果为 00010100

以上就是java中全部的位操作符,其中~是唯一的一元操作符,不能跟 = 号连用,其他的为二元操作符,可以跟 = 连用,比如 b = b & 2 可以写成 b &= 2,但是 i = ~2 不能写成 i ~= 2 。

关于java位运算的巧妙运用,网上有很多,就不再在这写一遍了,了解位运算主要是为了方便理解原码涉及到的位运算,比如学习IO中Bits类时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
   /*
     * 该方法提供了两个参数,第一个参数是一个byte型数组b,其中已经存放了字节数据,第二个参数off为数据读取的起点位置,从数组off索引处连续取出两个字节的数据
     * 假设第一个数据为c(b[off]),第二个数据为d(b[off+1]),通过一些小操作,将两个字节的数据组成一个char型数据,并返回,下面描述一下具体的操作,之后方法中的
     * 操作类似,便不在细讲了。
     * 假设我们现在用的是32位的机器,我们读取的第一个byte数据为00000000(高八位),第二个读取的byte数据为01100001(低八位)。
     * 先来看第一步(b[off+1] & 0xff),这里讲第二个数据与0xff进行了一次与操作,具体过程如下:
     * byte[off+1] :00000000 00000000 00000000 01100001
     *        0xff :00000000 00000000 00000000 11111111  &
     *        结果 :00000000 00000000 00000000 01100001
     * 第二步(b[off] << 8),具体过程如下:    
     *   byte[off] :00000000 00000000 00000000 00000000
     *                                                   << 8
     *        结果 :00000000 00000000 00000000 00000000
     * 第三步两者相加,具体过程如下:
     * byte[off+1] && 0xff :00000000 00000000 00000000 01100001
     *   byte[off] << 8    :00000000 00000000 00000000 00000000   +
     *                结果 :00000000 00000000 00000000 01100001
     * 第四步转换成char型:因为我们知道,java中char型占用两个字节,所有有效数字为后16位,即00000000 01100001,即十进制的97,即字母a。低位的&0xff操作是为了
     * 将非有效位的值全变为0,避免负数时,自动补1对运算的影响。
     * 最终将得到的char型返回。
     */
    static char getChar(byte[] b, int off) {
        return (char) ((b[off + 1] & 0xFF) +
                       (b[off] << 8));
    }