文章目录
在基于哈希表实现的Map中一个常用技巧就是将哈希桶的数量设置为2的n次方,也就是2n,此后通过取余操作定位key所在桶的位置可以转换成与运算。之所以将取余运算改成与运算,一方面这两者计算的结果是一样的,另外一方面是因为与运算具有更好的性能,因为与运算指令周期是小于取余运算的。Java中的 HashMap 和Go中 map 都使用到这个技巧。
基于哈希表实现的Map中的取余运算转换成与运算的技巧,用数学语言来表达:
对于正整数x, y,如果x为2的n次方,n为正整数,那么y%x=y&(x−1) 表达式是成立的。
对于y%x=y&(x−1)等式的成立有很多理解角度,本文从数学角度出发,尝试以数学证明方式来证明它。证明过程如下:
y为正整数,将y转换成二进制后,其十进制的值可以用如下表达式表示,其中a1,a2,… an分别表示y二进制表示时其第1位,第2位,第n位上的值,依次类推:
y=a1∗20+a2∗21+...+an∗2n−1+an+1∗2n+an+2∗2n+1+...
举例说明,比如y为25时候,其对应二进制为11001,那么上面表达式表示如下:
25=1∗20+0∗21+0∗22+1∗23+1∗24+0∗25+...+0∗2n−1+0∗2n+...
其中a1=1,a2=0,a3=0,a4=1,a5=1,a5之后的都为0,这里面为了方便理解,把a5之后都写出来了。
那么 y%x可以转换成:
y%x=(a1∗20+a2∗21+...+an∗2n−1+an+1∗2n+an+2∗2n+1+...)%x
由于x是2的n次方,所以x=2n,那么上面表达式可以转换为:
y%x=(a1∗20+a2∗21+...+an∗2n−1+an+1∗2n+an+2∗2n+1+...)%2n
进一步转换成:
y%x=((a1∗20+a2∗21+...+an∗2n−1)+(an+1∗2n+an+2∗2n+1+...))%2n
从上面可以看到我们把y分为从a1到an和从an+1到无穷这两部分,按照模运算规则:(a+b)%p=(a%p+b%p),上面表达式可以继续转换成如下:
y%x=((a1∗20+a2∗21+...+an∗2n−1)%2n+(an+1∗2n+an+2∗2n+1+...)%2n)%2n
由于(an+1∗2n+an+2∗2n+1+...)可以整除2n,上面表达式可以进一步简化为:
y%x=((a1∗20+a2∗21+...+an∗2n−1)%2n)%2n
而a1∗20+a2∗21+...+an∗2n−1可能的最大值为2n−1(当且仅当a1=a2=...=an=1时),也就是说a1∗20+a2∗21+...+an∗2n−1<2n, 那么存在:
(a1∗20+a2∗21+...+an∗2n−1)%2n=a1∗20+a2∗21+...+an∗2n−1
那么等式y%x=((a1∗20+a2∗21+...+an∗2n−1)%2n)%2n进一步可以简化为:
y%x=a1∗20+a2∗21+...+an∗2n−1
上面等式中a1∗20+a2∗21+...+an∗2n−1的含义是y二进制表示时候第1位到n位,其对应的十进制整数结果可以用与运算$ y;&;(20+21 + … + 2^{n-1})$得到,存在以下等式:
a1∗20+a2∗21+...+an∗2n−1=y&(20+21+...+2n−1)
y%x=a1∗20+a2∗21+...+an∗2n−1等式可以进一步简化处理得到我们要证明的等式:
y%x=y&(20+21+...+2n−1)=y&(2n−1)=y&(x−1)
综上所述,对于正整数x, y,如果x为2的n次方,n为正整数,那么y%x=y&(x−1) 表达式是成立的。