源码为
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;
}
该方法的作用是将i的二进制表示的数前面的0的个数返回,
由于负数第一位符号位是1,所以必然返回0
if (i >>> 16 == 0) { n += 16; i <<= 16; }
i >>> 16,这表示i向右移动16位,与i>>16不同
'>>右移时如果是正数会补0,负数会补1,而>>>只会补0,举例
8>>2 = 0100 -> 0001
8>>>2 = 0100 -> 0001
-8>>2 = 1100 -> 1111 //实际上负数是以补码形式保存,这里只是举例
-8>>>2 = 1100 -> 0011 // 高位补0
在看源码
i >>> 16 == 0
// 00000000 00000000 00000000 00000000
实际上表示的是前16位为0,
i <<= 16
表示将i左移16位后再赋值给i,整句的意思是如果i前16位为0,则把后16位移动到前16位,并给后16位补0。
通俗点讲就是,先判断前面16位是不是0,如果是0就把前16位去掉,后面的顶上。
后续的
if (i >>> 24 == 0) { n += 8; i <<= 8; }
i>>>24实际上判断的是8位,之后再判断前4位,再判断前2位
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;
n就是统计0的数量,但是我们注意到n的初始值为1,按理说不应该是0吗?
原因在最后一句,如果i>>>31即i的末位是0的话,n应该要再加上1,但是这里实际上是减去了末位的值0,即少加了1,而如果末位的值是1本应该跳过,但这里却多减了1,所以n的初始值不是0而是1.
n -= i >>> 31;
也可以看做
if (i >>> 31 == 1) { n -= 1; }
至于作者为什么要这样写,不得而知,如果你有什么看法欢迎留言讨论~
如果把n的初始值设为0,这个方法也可以这么写
public static int numberOfLeadingZeros(int i) {
// HD, Figure 5-6
if (i == 0)
return 32;
int n = 0;
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; }
if (i >>> 31 == 0) { n += 1;};
return n;
}
这种二分判断法方法让原来需要比较32次减少至比较5次,大大提高了效率
它还有一个姐妹方法,统计从右边开始的0个数,类似就不再叙述了
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);
}
网友评论