Integer类中的numberOfLeadingZeros和numberOfTrailingZeros方法

6 篇文章 0 订阅

一、引例

今天在阅读 Integer 类的源码的时候,发现了两个比较有意思的方法, 频繁的使用位运算进行左移和右移操作,

分别是 numberOfLeadingZeros 方法和 numberOfTrailingZeros 方法。

二、numberOfLeadingZeros

这个方法从 JDK 1.5 以后开始出现,主要功能是返回无符号整型 i 的最高非零位前面的 0 的个数。

例如

无符号整数 24 的二进制表示为 0000 0000 0000 0000 0000 0000 0001 1000,第五位的 1 为最高非零位,那么调用这个方法返回的就是该位以前 0 的个数,为 27。

我们先来看一下这个方法的源码:

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

一开始对频繁的移位操作以及移动的位数百思不解,查阅了相关资料后恍然大悟,这是二分法的经典应用,这个方法的思路是在寻找最高非零位的时候采用折半查找,因为 int 类型是 32 位的,所以先右移 16 位,判断左半部分是否为 0,如果为 0 的话,说明 1 在右半部分,然后将 i 左移 16 位为下面的操作做铺垫,因为此时已经确定高 16 位为 0 了,所以左移 16 位只是将最高非零位移到高 16 位上。如果第一个条件成立了,那么右移 24 位就相当于将原先的低 16 位右移 8 位来确定最高非零位是在低 8 位还是高 8 位上,后面的操作类似,不再一一推导了。

n 变量用来存放 0 的个数,在每次的操作中,如果条件成立,那么就用 n 变量加上 0 的个数。

最后的 n -= i >>> 31 是对最后两位数的判断,因为在上一行代码中,只要条件成立就将 n 的值加 2,并没有再往下判断剩下的两位 0 的个数,因为 n 的初始值为 1,所以默认剩下的两位中最后 1 位是 1,因此最后还要判断是到底是哪一位为 1,如果 i >>> 31 为 0,那么说明最后一位为 1,就不需要减了,如果不为 0,说明倒数第二位为 1,就需要减去 1。

例如:

无符号整数 24 的二进制表示为 0000 0000 0000 0000 0000 0000 0001 1000,调用此方法的执行流程如下:

  1. 右移 16 位的值为 0,说明在最高非零位在低 16 位上,n = 17,i 左移 16 位后为 0000 0000 0001 1000 0000 0000 0000 0000
  2. 右移 24 位的值为 0,说明在最高非零位在低 16 位上的低 8 位上,n = 25,i 左移 8 位后为 0001 1000 0000 0000 0000 0000 0000 0000
  3. 右移 28 位的值为 1,不为 0,说明在最高非零位在低 16 位的低 8 位的高 4 位上
  4. 右移 30 位的值为 0,说明在最高非零位在低 16 位的低 8 位的高 4 位的低 2 位上,n = 27,i 左移 2 位后为 0110 0000 0000 0000 0000 0000 0000 0000
  5. 最后,i 右移 31 位为 0,不需要减 1,最终答案为 27

三、numberOfTrailingZeros

这个方法也是从 JDK 1.5 以后开始出现,主要功能是返回无符号整型 i 的最低非零位后面的 0 的个数。

例如

无符号整数 24 的二进制表示为 0000 0000 0000 0000 0000 0000 0001 1000,第四位的 1 为最低非零位,那么调用这个方法返回的就是该位以后 0 的个数,为 3。

我们先来看一下这个方法的源码:

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

这两个方法在编码风格上有一点小的出入,但是思想都是一样的,都是采用二分法,推导过程与 numberOfLeadingZeros 类似,就不详细推导了,读者有兴趣可自行推导。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值