HashMap那些事

一、hashMap的工作原理
HashMap是基于链地址法的原理,使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。

   当我们给put()方法传递键和值时,我们先对键调用hashCode()方法计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍),储存Node对象。

   使用get(key)从HashMap中获取对象时,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。

二、hash 算法和寻址算法

hash() 方法

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

h = key.hashCode() 表示 h 是 key 对象的 hashCode 返回值;
h >>> 16 是 h 右移 16 位,因为 int 是 4 字节,32 位,所以右移 16 位后变成:左边 16 个 0 + 右边原 h 的高 16 位;
最后把这两个进行异或返回。

异或:二进制位运算。如果一样返回 0,不一样则返回 1。

putVal() 中寻址部分

tab[i = (n - 1) & hash]

tab 就是 HashMap 里的 table 数组 Node[] table ;
n 是这个数组的长度 length;
hash 就是上面 hash() 方法返回的值;

1、寻址为什么不用取模?

对于上面寻址算法,由于计算机对比取模,与运算会更快。所以为了效率,HashMap 中规定了哈希表长度为 2 的 k 次方,而 2^k-1 转为二进制就是 k 个连续的 1,那么 hash & (k 个连续的 1) 返回的就是 hash 的低 k 个位,该计算结果范围刚好就是 0 到 2^k-1,即 0 到 length - 1,跟取模结果一样。
也就是说,哈希表长度 length 为 2 的整次幂时, hash & (length - 1) 的计算结果跟 hash % length 一样,而且效率还更好。

利用位运算代替取模运算,可以大大提高程序的计算效率。位运算可以直接对内存数据进行操作,不需要转换成十进制,因此效率要高得多。

2、为什么不直接用 hashCode() 而是用它的高 16 位进行异或计算新 hash 值?

int 类型占 32 位,可以表示 2^32 种数(范围:-2^31 到 2^31-1),而哈希表长度一般不大,在 HashMap 中哈希表的初始化长度是 16(HashMap 中的 DEFAULT_INITIAL_CAPACITY),如果直接用 hashCode 来寻址,那么相当于只有低 4 位有效,其他高位不会有影响。这样假如几个 hashCode 分别是 210、220、2^30,那么寻址结果 index 就会一样而发生冲突,所以哈希表就不均匀分布了。
为了减少这种冲突,HashMap 中让 hashCode 的高位也参与了寻址计算(进行扰动),即把 hashCode 高 16 位与 hashCode 进行异或算出 hash,然后根据 hash 来做寻址。

你可能感兴趣的:(数据结构)