java java.lang.Long详解之三 大显神通的位移运算

文章看事后感受受益不浅,因此留下了以备温故:http://www.congmo.net/blog/2012/03/11/Long-ByteShifting/
java

本篇主要讲述位移运算在Long中所扮演的重要角色,有些出神入化的我根本没法理解,可是我一直秉承着不管对错都要记录思考的过程的宗旨写每一篇文章。这一篇也不例外,读Long这个类确实须要比较普遍的知识面,我也是一边在OSChina和stackoverflow上提问,一边慢慢的钻研,不免会存在误差。web

先来看个简单的。
算法

public static int signum(long i) {
  // HD, Section 2-7
  return (int) ((i >> 63) | (-i >>> 63));
}

这个函数做用就是返回参数i的符号,若是返回-1则是负数,若是返回0则是0,若是返回1则是正数。算法就是(int) ((i >> 63) | (-i >>> 63)),若是是正数的话i有符号右移63位后为0,-i无符号右移63位以后结果为1,或操做以后结果就是1.若是i为负数,那么有符号右移63位后就变成了1,而后-i无符号右移63位后就只剩下符号位,最后作或(|)操做结果就是-1. 若是参数i为0,那么移位后结果就是0.
函数

System.out.println(Long.signum(100L));
System.out.println(Long.signum(0L));
System.out.println(Long.signum(-100L));
 
输出结果:
1
0
-1

接着是一个不多用到,可是实现方式不错的两个方法,循环左移和循环右移方法。
ui

public static long rotateLeft(long i, int distance) {
    return (i << distance) | (i >>> -distance);
}
public static long rotateRight(long i, int distance) {
    return (i >>> distance) | (i << -distance);
}

实现的代码量能够说已经精简到最少了,有一点要注意的是,循环移位时,参数distance能够接受负数,当distance为负数时,这个等式是成立的,rotateLeft(i, distance) = rotateRight(i, -distance)。这个方法中有两点值得借鉴的,第一从总体上讲循环移位的实现方式;第二是distance与-distance的巧妙运用。google

就拿循环左移先来讲说第二点吧,前置条件,咱们首先假设distance大于0,起先我是很不理解i >>> -distance的,后来在stackoverflow上发问,有人给出了解释,在移位的时候,若是distance小于0,会根据被移位数的长度进行转换。就好比说这里咱们对long进行移位,那么-distance就会被转换成(64 + distance)(注,这里的distance是小于0的)。这样的话,若是distance大于0时,(i << distance) | (i >>> -distance);就会被转化成(i << distance) | (i >>> 64 + distance);spa

清楚了第二点,那么第一点也就不难理解了。用一幅图来解释循环左移。

在distance大于0的前提下,先左移distance位,而后再右移64-distance,最终用或运算相加,就是循环移位的结果。图中为了省事儿用了8位作了个演示,先左移3位,而后右移(8-3)位,或运算以后就是结果啦。关于-distance在stackoverflow上的提问在这里.net

下面是个更给力的方法-reverse(long i),能够说就是高效率的化身。code

public static long reverse(long i) {
    // HD, Figure 7-1
    i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L;
    i = (i & 0x0f0f0f0f0f0f0f0fL) << 4 | (i >>> 4) & 0x0f0f0f0f0f0f0f0fL;
    i = (i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL;
    i = (i << 48) | ((i & 0xffff0000L) << 16) |
            ((i >>> 16) & 0xffff0000L) | (i >>> 48);
    return i;
}

从总体上说,这个reverse方法集移位与二分算法于一身,堪称经典。 第一步以单位为单位,奇偶位交换 第二步以两位为单位,完成先后两位的交换。 第三步以四位为单位,完成先后四位的交换。 第四步以八位为单位,完成先后八位的交换。 最后一步没有按常理继续二分,而是经过一个转换一步就完成了以16和32位为单位的交换。进而结束了整个64位的反转。orm

如今一步一步剖析都是如何实现的。

i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L;

16进制的5为0101,或操做前半部分首先取出i的全部奇数位,而后总体左移一位,这样实现i的奇数位左移一位变成偶数位;或操做后半部分先右移,即将偶数位右移变成奇数位,而后再取出奇数位。这样就完成了64位中奇数位与偶数位的交换。

i = (i & 0x3333333333333333L) << 2 | (i >>> 2) & 0x3333333333333333L;

这句一样是实现交换,只不过3对应的16进制为0011,即本次交换以2个字节为单位,交换完成了4个字节的反转。 Liquid error: Flag value is invalid: -O ”” 直到这行代码,实现了以字节为单位的反转,最后仅仅使用一行代码就实现了一两个字节和四个字节为单位的反转。 为了方便画图,如今对操做进行编号,另外从以字节为单位交换开始,以前的细节忽略。
(i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL;
(i << 48)
(i & 0xffff0000L) << 16)
(i >>> 16) & 0xffff0000L)
(i >>> 48);
这幅图描述每一个编号代码执行以后64位的变化。

很少作解释,因为这个reverse的最后一行不是按常理”出牌”,因此我使用纯粹的二分法来实现reverse。
public static long reverse(long i) {
  // HD, Figure 7-1
  i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L;
  i = (i & 0x3333333333333333L) << 2 | (i >>> 2) & 0x3333333333333333L;
  i = (i & 0x0f0f0f0f0f0f0f0fL) << 4 | (i >>> 4) & 0x0f0f0f0f0f0f0f0fL;
  i = (i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL;
  i = (i & 0x0000ffff0000ffffL) << 16 | (i >>> 16) & 0x0000ffff0000ffffL;
  i = (i & 0x00000000ffffffffL) << 32 | (i >>> 32) & 0x00000000ffffffffL;
  return i;
}

至于为何要采用那种方式,而不是用”纯粹”的二分法,在stackoverflow上有人提到,多是由于前一种实现方式须要9个操做,然后一种实现方式须要10个操做。具体是出于怎样的目的可能只有做者才知道。关于reverse我在stackoverflow上的提问在这里

最后看一个方法。

public static int bitCount(long i) {
    // HD, Figure 5-14
    i = i - ((i >>> 1) & 0x5555555555555555L);
    i = (i & 0x3333333333333333L) + ((i >>> 2) & 0x3333333333333333L);
    i = (i + (i >>> 4)) & 0x0f0f0f0f0f0f0f0fL;
    i = i + (i >>> 8);
    i = i + (i >>> 16);
    i = i + (i >>> 32);
    return (int)i & 0x7f;    
}
这个方法是返回一个long类型的数字对应的二进制中1的个数,其实google上有不少种,这里采用的叫平行算法实现的,算法以下图。

可是这个方法的第一行又采起了一个特别的方式实现i中奇数位+偶数位,我有点儿没想明白。整体上就是像图中所示那样,相邻的两位相加的结果再相邻的四位相加,最后获得二进制中1的个数。 还有一点值得提一下,就是最后一行与上7f,由于long类型,1的个数最多也不会超过64个,因此只取最后7位便可。
相关文章
相关标签/搜索