您的位置:首页 > 其它

Integer中的相关函数实现源码分析

2016-08-31 15:43 501 查看
1. Integer.bitCount

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;
}


分析:开始死活都没看懂是什么原理,后来网上搜了一顿,大都是从同一篇文章转过去的,说的也不是很清楚,后来翻了Hacker’s Delight,才算是弄明白了。过程推导如下:

为了快速的求整数中二进制表示中1的位数,采用了二分法,思想如下:首先设置每个2位字段为原来的两个单个位的和(即开始每两个bit位为一组,组内求和),将结果放入相应的中间数中,如何对中间数重新分组,每4位一组,组内求和,以此类推。

实例如下:



这个思想和归并排序类似,要统计32个bit位的和,先分成两组,每组16bit,分别求和,然后将和加起来。而对于16bit数据的求和,转化合成8bit的求和,以次类推。

算法的复杂度为:O(logN) (因为归并结果的复杂度为O(1),复杂度由主定理得出)

上面的思想用代码表示就是:

i= (i&0x55555555) +  ((i>>>1)&0x55555555 );
i= (i&0x33333333) +  ((i>>>2)&0x33333333 );
i= (i&0x0F0F0F0F) +  ((i>>>4)&0x0F0F0F0F );
i= (i&0x00FF00FF) +  ((i>>>8)&0x00FF00FF );
i= (i&0x0000FFFF) +  ((i>>>16)&0x0000FFFF );

return i ;


这种样子就比较好理解了。但是上面的代码还可以优化(显然,不然JDK里就是用这个样子的了)。

优化思路:

最后一个与没有必要?

并且当资格字段的和不会产生到邻近字段的进位时,相关的与也是何以省略的(int型数据最多32个1,相邻4个bit加起来的和最多为4,相邻8个bit加起来的和最多为8,相邻16个bit加起来的和最多为16,即步骤中的i&0x0F0F0F0F=i, i&0x00FF00FF=i,i&0x0000FFFF=i)。

第一行的代码可以通过下面的公式简化:(原理还没看懂):

pop(x)=x- x/2 -x/4 - ….. -x/(2^31);

通过上面的化简,就变成了JDK中的样子了。

2. Integer.highestOneBit

public static int highestOneBit(int i) {
// HD, Figure 3-1
i |= (i >>  1);
i |= (i >>  2);
i |= (i >>  4);
i |= (i >>  8);
i |= (i >> 16);
return i - (i >>> 1);
}


原理分析:这个不好解释,举例说明吧,假如求X的最高有效位,假如最高位为第m位,则从低到高第m位一定为1.之后的m-1位可为0,可为1。这个求highestOneBit的思想是先将X变成Y,其中Y中的右边m位全为1.则X和Y的最高位相同,且Y+1=2^m,则结果值就是将Y的底m-1位清零,返回。

如X=1010,Y=1111。

Y-Y>>>1=1111-0111=1000

3. Integer.lowestOneBit

public static int lowestOneBit(int i) {
// HD, Section 2-1
return i & -i;
}


这个就不解释了,比较简单。(因为-i和i的最后一位1位置是相同的)

4. Integer.numberOfLeadingZeros

public static int numberOfLeadingZeros(int i) {
// HD, Figure 5-6
if (i == 0)
return 32;
int n = 1;
if (i >>> 16 == 0) { n += 16; i <<= 16; }
if (i >>> 24 == 0) { n +=  8; i <<=  8; }
if (i >>> 28 == 0) { n +=  4; i <<=  4; }
if (i >>> 30 == 0) { n +=  2; i <<=  2; }
n -= i >>> 31;
return n;
}


原理分析:就是一步一步的数前面0的个数,但是这个算法的一个优点就是比较快的收缩了。如果i高16位为0,那就count+16,在加上低16位中高位中0的个数,为了算低16位中前导0的个数,将低16位放到前面。在低16位的计算中,如果高8位为0,则结果加8,同时将后8位放到最前,以此类推。

最后一条语句n -= i >>> 31,是下面两条语句的优化

int n = 0;
if (i >>> 31 == 0) //16+8+4+2
{ n +=  1;  }


5. Integer.numberOfTrailingZeros

public static int numberOfTrailingZeros(int i) {
// HD, Figure 5-14
int y;
if (i == 0) return 32;
int n = 31;
y = i <<16; if (y != 0) { n = n -16; i = y; }
y = i << 8; if (y != 0) { n = n - 8; i = y; }
y = i << 4; if (y != 0) { n = n - 4; i = y; }
y = i << 2; if (y != 0) { n = n - 2; i = y; }
return n - ((i << 1) >>> 31);
}


原理分析:前导0的逆运算

6.Integer.toBinaryString()

public static String toBinaryString(int i) {
return toUnsignedString0(i, 1);
}

private static String toUnsignedString0(int val, int shift) {
// assert shift > 0 && shift <=5 : "Illegal shift value";
int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
char[] buf = new char[chars];

formatUnsignedInt(val, shift, buf, 0, chars);

// Use special constructor which takes over "buf".
return new String(buf, true);
}


需要注意的是转化成的string的位数为32-Integer.numberOfLeadingZeros();

7. 总结

几个二进制运算的技巧:

将最右侧的1改为0:x&(x-1)

检查无符号整数是否为2的幂:x&(x-1) ==0

检查无符号整数是否为2^n -1的形式:x&(x+1) ==0

求最右侧的1:x&(-x)

求最右侧的0:-x&(x+1)

构造识别后缀0的掩码,如果x=0,则生成所有位都为1的字(如0101000=>0000111)

~x &(x-1) 或者 ~(x| -x)或者 (x& -x)-1
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  源码 Integer