巧妙使用位运算

java中的二进制操作很多地方都有很好的使用,比如HashMap源码中就利用了当n等于2的次幂时,"hash%n"和"hash&(n-1)"的特性,下面再列举几个巧妙使用位运算的例子。

HashMap

hashMap中再计算key存储在tab数组的什么位置时,计算方式是int index = (n - 1) & hash
这主要有两个好处

  1. tab数组长度的一般是2的n次方,这时该操作等价于hash%n,但执行速度更快
  2. hash可能是负数,%操作的结果可能仍然是负数,但是&操作结果肯定为整数。

SpacePadder填充固定位

假设又一个StringBuffer要求要有n(20或者更多)个字符,如果不足,向右填充空格直到满足为止,一般最简单的方式就是for循环,一个个append直到满足n个字符,但是效率就不高了,牛逼的做法如下

public class SpacePadder {

    final static String[] SPACES = { " ", "  ", "    ", "        ", // 1,2,4,8
            // spaces
            "                ", // 16 spaces
            "                                " }; // 32 spaces

     /**
     * Fast space padding method.
     */
    final static public void spacePad(StringBuilder sbuf, int length) {
        
        //当length>=32时,一次性追加32个空格
        while (length >= 32) {
            sbuf.append(SPACES[5]);
            length -= 32;
        }

        //当length <32时,根据具体值,一次次追加 16, 8, 4, 2, 1个字符,则32以内的
        //任意个空格都可以在5次操作下完成。
        for (int i = 4; i >= 0; i--) {
            if ((length & (1 << i)) != 0) {
                sbuf.append(SPACES[i]);
            }
        }
    }
}

巧妙的利用了32与位操作,最好地解决了追加任意个字符的问题。

Integer的numberOfLeadingZeros

Integer的前导0的个数,返回Integer最高非0位的前面0的个数。
比如 00001111 00000000 00000000 00000000 的前导0个数为4。
jdk8源码

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的个数可能从0到32,如果结果为3,则反推i可能为
i = 0b 00010000_00000000_00000000_00000000

走到图中第4步, 表示i的高两位肯定为0

n = 1+2=3, 
i = 0b01000000_00000000_00000000_00000000

由于前两位只可能为01,10,11,右移31的结果只能为0或者1,当原始i的高第三位为0时,i>>>310,n=3,没问题; 当原始i的高第三位为1时,i>>>311,n=3-1=2,也没问题。

参考 https://www.jianshu.com/p/2c1be41f6e59

Integer的numberOfTrailingZeros

int的后导0的个数,也是以2为底的对数

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^x=x;
x^x=0;

算法题示例

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

Integer.signum

计算一个int的符号,正数返回1,负数返回-1,0返回0。

public static int signum(int i) {
        // HD, Section 2-7
        return (i >> 31) | (-i >>> 31);
}

对于正数, i>>31带符号右移31位,结果为0
对于负数,i>>31带符号右移31位, 结果为11111111_11111111_11111111_11111111;

对于正数,-i>>>31无符号右移31位,-i为负数,最高位为1,无符号右移31位,则结果为1;
对于负数,-i>>>31无符号右移31位, -i为正数, 最高位为0, 无符号右移31位,则结果为0;

综上,两部分再|,则

对于正数,最终结果为1;
对于负数,最终结果为11111111_11111111_11111111_11111111, 由于计算机中存储的是补码,所以真实值为-1;

n个bit所能表示的最大正数

n<=64;
1个bit能表示的最大正数为1;
2个bit能表示的最大正数为3;
3个bit能表示的最大正数为7;

64个bit能表示的最大正数为64;
如何函数表示呢?lucenePackedInts类中有

public static long maxValue(int bitsPerValue) {
    return bitsPerValue == 64 ? 
    // 0取反则是连续64个1
    // 左移n位,则低n位都是0,初次之外其他位都是1
    // 再取反,则低n位都是1,其他位都是0
    // 比如n=2,结果为 xxxx11 = 3
    // n=3,结果为xxx111=7 
    // 构思巧妙
    Long.MAX_VALUE : ~(~0L << bitsPerValue);
}

计算一个long值需要多少个bit保存

参考lucenePackedInts

public static int unsignedBitsRequired(long bits) {
    return Math.max(1, 64 - Long.numberOfLeadingZeros(bits));
  }

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

获取>=m的且是n的倍数的最小数

节选自lucene

/** 
   * Aligns an object size to be the next multiple of {@link #NUM_BYTES_OBJECT_ALIGNMENT}.
   * >=size且是8的倍数的最小值
   */
  public static long alignObjectSize(long size) {
    size += (long) NUM_BYTES_OBJECT_ALIGNMENT - 1L;
    return size - (size % NUM_BYTES_OBJECT_ALIGNMENT);
  }

一般我们这样设计

if(size % NUM_BYTES_OBJECT_ALIGNMENT) == 0{
return size;
}

return size - (size % NUM_BYTES_OBJECT_ALIGNMENT) 
+ NUM_BYTES_OBJECT_ALIGNMENT;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值