JDK-lang包里的一个方法:
java.lang.Integer.numberOfLeadingZeros(int)
这个方法是用来计算int的二进制值从左到右有连续多少个0。
我们知道Java里的int型是有负数的,负数的二进制第1位肯定是1,所以如果参数i小于0,应该直接返回0就可以了。
但我们看JDK里的实现:
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;
}
可以看到JDK里的实现里并没有对负数的情况进行判断,而是走了下面4个if分支判断和位移操作,这在参数i 为负数的情况下肯定是不必要的,而且也会影响性能。
如果继续挖掘的话,可以看到代码里有一行注释:
// HD, Figure 5-6
注释里HD的意思是指《Hacker’s Delight》这本书,意思是该方法的实现逻辑参考了这本书,那我们就来看下《Hacker’s Delight》书里的实现代码:
int nlz(unsigned x) {
int n;
if (x == 0) return(32);
n = 1;
if ((x >> 16) == 0) {n = n +16; x = x <<16;}
if ((x >> 24) == 0) {n = n + 8; x = x << 8;}
if ((x >> 28) == 0) {n = n + 4; x = x << 4;}
if ((x >> 30) == 0) {n = n + 2; x = x << 2;}
n = n - (x >> 31);
return n;
}
可以看到这是C语言的实现,乍一看,貌似和JDK里的Java实现是一样的,但仔细看,你会发现方法参数x是unsigned类型的,因为unsigned类型不会有负数,不需要判断负数的情况,所以《Hacker’s Delight》书里的实现是没问题的,而JDK实现里的方法参数是int类型,是需要判断负数的情况的。
所以我们可以大胆的推测,JDK的程序员当初在实现这个方法的时候,只是把
《Hacker’s Delight》书里的实现改为了Java实现,而没有考虑到参数类型的区别。
这个方法在JDK里是被标记为intrinsic的,意思是该方法针对不同的硬件有更底层的原语级的实现,但这并不代表Java实现的逻辑就不需要被优化,因为:
1.在有些硬件上可能没有intrinsic支持。
2.用户显式关闭了intrinsic调用。
3.没有开启JIT编译。
除了Integer类之外,Long类里也有相同功能的方法需要改进:
java.lang.Long.numberOfLeadingZeros(long)
我给OpenJDK提了bug,其实应该叫改进/增强(Enhancement),OpenJDK官方经过测试,发现修改后的方案确实会提升性能,于是愉快的修复了这个问题:https://bugs.openjdk.java.net/browse/JDK-8189230
不过大家可能要等到Java-11发布的时候才能看到这个修改了。:)