Integer中的相关函数实现源码分析
2016-08-31 15:43
501 查看
1. Integer.bitCount
分析:开始死活都没看懂是什么原理,后来网上搜了一顿,大都是从同一篇文章转过去的,说的也不是很清楚,后来翻了Hacker’s Delight,才算是弄明白了。过程推导如下:
为了快速的求整数中二进制表示中1的位数,采用了二分法,思想如下:首先设置每个2位字段为原来的两个单个位的和(即开始每两个bit位为一组,组内求和),将结果放入相应的中间数中,如何对中间数重新分组,每4位一组,组内求和,以此类推。
实例如下:
![](https://img-blog.csdn.net/20160831120411963)
这个思想和归并排序类似,要统计32个bit位的和,先分成两组,每组16bit,分别求和,然后将和加起来。而对于16bit数据的求和,转化合成8bit的求和,以次类推。
算法的复杂度为:O(logN) (因为归并结果的复杂度为O(1),复杂度由主定理得出)
上面的思想用代码表示就是:
这种样子就比较好理解了。但是上面的代码还可以优化(显然,不然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
原理分析:这个不好解释,举例说明吧,假如求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
这个就不解释了,比较简单。(因为-i和i的最后一位1位置是相同的)
4. Integer.numberOfLeadingZeros
原理分析:就是一步一步的数前面0的个数,但是这个算法的一个优点就是比较快的收缩了。如果i高16位为0,那就count+16,在加上低16位中高位中0的个数,为了算低16位中前导0的个数,将低16位放到前面。在低16位的计算中,如果高8位为0,则结果加8,同时将后8位放到最前,以此类推。
最后一条语句n -= i >>> 31,是下面两条语句的优化
5. Integer.numberOfTrailingZeros
原理分析:前导0的逆运算
6.Integer.toBinaryString()
需要注意的是转化成的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
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
相关文章推荐
- 基础c字符串相关函数的源码实现
- PHP strtotime函数用法、实现原理和源码分析
- AtomicInteger源码分析——基于CAS的乐观锁实现
- Glide源码分析(六)——从DecodeJob相关实现看图片加载流程
- zepto 源码分析2 - 编码技巧 & 函数实现
- TCP/IP协议栈源码图解分析系列10:linux内核协议栈中对于socket相关API的实现
- 蔡军生先生第二人生的源码分析(五十七)OpenGL离屏渲染的相关函数
- 可变参数列表实现机制与printf()函数源码分析
- libevent源码分析(6)--2.1.8--创建和释放libevent句柄event_base的相关函数
- Libevent源码分析-----与event相关的一些函数和操作
- ThreadPoolExecutor的应用和实现分析(中)—— 任务处理相关源码分析 线程利用(转)
- ThreadPoolExecutor的应用和实现分析(中)—— 任务处理相关源码分析
- JVM:锁实现(synchronized&JSR166)行为分析和相关源码
- linux epoll模型源码分析 一 函数实现
- Linux内核源码分析--内存管理(二、函数实现技巧)
- AtomicInteger源码分析——基于CAS的乐观锁实现
- (2.1.2.7)ThreadPoolExecutor的应用和实现分析(中)—— 任务处理相关源码分析
- 【libevent】源码分析(4)--与event相关的一些函数和操作
- Linux内核源码分析--内存管理(二、函数实现技巧)
- AtomicInteger源码分析——基于CAS的乐观锁实现