关于历史上最简单的一道Java面试题思考(Java位移和取模)

关于历史上最简单的一道Java面试题思考(Java位移和取模)

引用几位大佬的文章:
历史上最简单的一道Java面试题,但无人能通过
全网把Map中的hash()分析的最透彻的文章,别无二家。
java中右移运算符>>和无符号右移运算符>>>的区别

题目很简单,完成代码,判断一个整数是否是奇数:

public boolean isOdd(int i)

在平时工作比较喜欢简洁代码的同学可能很快想到自己想象的最优解:

       public boolean isOdd(int i) {
        return i % 2 == 1;
    }

这个其实有缺陷,至于为什么不是这个,因为你忽略了,负数也是有奇偶的。正确应该是如下:

    public boolean isOdd(int i) {
        return i % 2 != 0;
    }

但是这个也不是最好的方式,有些同学可能会想到位移去操作,可能总是觉得位移比较高级,是大多数问题的最好解决方式。于是有了以下:

    public boolean isOdd(int i) {
        return i >> 1 << 1 != i;
    }

终于此道题的最终解来了,不知道有没有同学想到了以下:

    public boolean isOdd(int i) {
        return (i & 1) == 1;
    }

但是我们实际代码测试过,发现上面的按位与操作和取模操作,实际运行的时间是差不多的,为什么呢?正题开始:

X % 2^n = X & (2^n - 1)

2^n表示2的n次方,也就是说,一个数对2^n取模 == 一个数和(2^n - 1)做按位与运算 。
假设n为3,则2^3 = 8,表示成2进制就是10002^3 -1 = 7 ,即0111。
此时X & (2^3 - 1) 就相当于取X的2进制的最后三位数。
从2进制角度来看,X / 8相当于 X >> 3,即把X右移3位,此时得到了X / 8的商,
而被移掉的部分(后三位),则是X % 8,也就是余数。

可以以实际例子演示:

6 % 8 = 66 & 7 = 6
10 % 8 = 210 & 7 = 2

关于历史上最简单的一道Java面试题思考(Java位移和取模)_第1张图片

同样在HashMap In Java 7中也使用了:

final int hash(Object k) {
   int h = hashSeed;
   if (0 != h && k instanceof String) {
       return sun.misc.Hashing.stringHash32((String) k);
   }

   h ^= k.hashCode();
   h ^= (h >>> 20) ^ (h >>> 12);
   return h ^ (h >>> 7) ^ (h >>> 4);
}

static int indexFor(int h, int length) {
   return h & (length-1);
}

indexFor方法其实主要是将hash生成的整型转换成链表数组中的下标。那么return h & (length-1);是什么意思呢?其实,他就是取模。Java之所有使用位运算(&)来代替取模运算(%),最主要的考虑就是效率。
位运算(&)效率要比代替取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。
所以,return h & (length-1);只要保证length的长度是2^n的话,就可以实现取模运算了。而HashMap中的length也确实是2的倍数,初始值是16,之后每次扩充为原来的2倍。

以上就是关于java中取模和位运算的一点理解。希望能帮助同学进一步理解Java的代码之美。

你可能感兴趣的:(java,java,数据结构,面试,算法)