二进制表示法以及Java 移位操作符的介绍

现在系统实现中,加法操作与移位操作运算速度差距不大,但是移位操作在做乘法的时候要快于乘法(减法是变相的加法,除法是变相的乘法)。在一些对运算速度要求高的系统中,移位操作往往能增加不少的效率。

要掌握移位操作符,首先要对二进制有一定的了解。

jdk中计算某一个二进制数之中1的数量的代码:

public static int bitCount(int i) {
        // HD, Figure 5-2
        i = i - ((i >>> 1) & 0x55555555);
        i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
        i = (i + (i >>> 4)) & 0x0f0f0f0f;
        i = i + (i >>> 8);
        i = i + (i >>> 16);
        return i & 0x3f;
    }

 

如果不清楚十进制如何转换成二进制,可以这样打印出来。

System.out.println(Integer.toBinaryString(2));//结果是10

二进制表示法,我们以int类型的2举例。运行结果是10,但是实际上它是 0000 0000 0000 0000 0000 0000 0000 0010。

(一般的,我们将左16位作为高位,右16位作为低位)。最高为为符号位,0代表正数,1代表负数

不过如果你打印这样的二进制。

System.out.println(00000000000000000000000000000010);

运行的结果居然是8!这不是二进制表示法除了问题,也不是编译器除了问题。编译器默认将操作数当成十进制做处理,以0开头的,编译器都会将它作为八进制处理,以0x开头就是16进制。

再测试一个 -2。

System.out.println(Integer.toBinaryString(-2));//11111111111111111111111111111110

Java是数值表示是基于二进制补码的。求一个数的负数,先对所有位求反,0变1,1变0,然后加一。就这个例子来说,先把2的二进制00000000000000000000000000000010 对每一位求反,获得结果11111111111111111111111111111101然后加1,最后结果是11111111111111111111111111111110。

 

二进制粗略的介绍就到这里,接下来介绍移位操作。

右移一个单位(>>1):往高位插入一个0,其他位都右移(相当于除以2)。左移一个档位(<<1):低位插入一个0,其他位都左移(相当于乘以2)。<< 和 >> 都是带符号的操作符,在移位后,如果数值本来是正的,最高位补0,负的就补1。其他的一位操作符还有 >>>,无符号右移操作符,移位后不会去补符号位(可见,这个操作符运算速度要快于有符号的移位操作符)

 

接下来看一个有趣的例子。例子来自Java puzzles。

public static void main(String[] args) {
int i = 0;
while (-1 << i != 0)
i++;
System.out.println(i);
}

按照我们所知道的知识, -1可16进制表示为0xffffffff(二进制为11111111111111111111111111111111)。int 类型的-1是32位都被置位的数值,每一次右移,都在低位插入一个0,第一次变成了:11111111111111111111111111111110,第二次变成了11111111111111111111111111111100…………所以循环了32次然后打印出我们32??但是在运行的时候,我们发现它不打印任何数值,而是进入了一个无限循环。

 

这个问题的谜题就在于-1移动32位的结果不是0,而是返回自身(我并不清楚底层是怎么做到的)。

在移动到 31位的时候:

System.out.println(-1 << 31);//-2147483648
System.out.println(Integer.toBinaryString(-2147483648));//10000000000000000000000000000000

但是移动到32位的时候变回了-1.

System.out.println(-1 << 32);

对于这个问题,我们可以打印一下Integer.MIN_VALUE。它表示int类型的最小数值。

System.out.println(Integer.MIN_VALUE);//-2147483648
System.out.println((-1 << 31) == Integer.MIN_VALUE);//true

我们发现-1左移31位的时候就已经等于 Integer.MIN_VALUE了。我们再比较一下

    int i = -1 << 31 ;
        System.out.println(i << 1);//0
        System.out.println(-1 << 32);//-1
        System.out.println(Integer.MIN_VALUE<<1);//0

这个意思就是,不能一下子丢弃所有的位。

关于移位操作,它还有这样的规定,对于int 类型,移位只有对低5位作为移动长度,对于long,为6位。(2的5次方32位,2的6次方64位)。而且移位长度是对32取余的,也就是说长度在0到31之间,对于long,在0到63之间。没有任何的移位方法可以让一个数值丢弃所有的位。

不过为了打印出 32,我们可以这样处理。这个循环可以输出32。

public static void main(String[] args) {
int distance = 0;
for (int val = -1; val != 0; val <<= 1)
distance++;
System.out.println(distance);
}

这个解题思路和前一个不同。前一循环判断可以一次性移动多少位,而这个循环每移动一位就存储到变量中。

 

这里还有一个很有趣的问题:

System.out.println(Integer.MIN_VALUE<<1);//-2147483648
        System.out.println(Integer.MAX_VALUE);//2147483647
        System.out.println(Math.abs(Integer.MIN_VALUE));//-2147483648

int最小值的绝对值比最大值大1.而且Math的abs方法对最小值取绝对值还是最小值。 

 

接下来再看一个例子

public static void main(String[] args) {
        int i = -1 ;
        int count = 0;
        while( i != 0){
            i >>>= 1;
//System.out.println(i); count
++; } System.out.println(count); }

接受了上一次的教训,也许你要脱口而出,无限循环!!!!我们分析这个程序,初始的时候,-1满足条件进入循环,然后做无符号右移操作.第一次移位操作,-1就变成了2147483647,之后每一次右移就是除以2。把注释去掉,可以打印如下结果。

2147483647
1073741823
536870911
268435455
134217727
67108863
33554431
16777215
8388607
4194303
2097151
1048575
524287
262143
131071
65535
32767
16383
8191
4095
2047
1023
511
255
127
63
31
15
7
3
1
0

 

接下来再看一段代码

public static void main(String[] args) {
        short i = -1 ;
        int count = 0;
        while( i != 0){
            i >>>= 1;
//            System.out.println(i);
            count++;
        }
        System.out.println(count);
        
    }

这段代码和上一段代码几乎一模一样,第一反应是16。short 类型的存储空间就是16位,再一想就迷糊了,byte,short,char三种类型进行运算的时候自动会转型成int类型,那么答案是32呢还是16呢。

不过运行这个程序,也许会让你大吃一惊。居然是一个无限循环!!!

首先在进行移位操作前,short类型的i被提升为int类型进行操作。这样原来的short 的-1(0xffff)变成了int的-1(0xffffffff)。接下来是无符号的右移,变成了int的 0x7fffffff(这个数字是2147483647)。>>>=是一个混合类型操作,第一步是移位,第二部就要赋值了。于是接下来发生了一个可怕的操作,要把int的数值存储到short类型的变量中,要进行窄化的原始类型转换。执行的结果是高位直接被截断,int的0x7fffffff又变成了short 的-1(0xffff)。于是进入了一个无限循环。

 

 

 

posted @ 2013-03-06 20:02  cainiaofeifei  阅读(4577)  评论(1编辑  收藏  举报