HashMap里的Hash
HashMap内部维护了一个链表数组table,每个元素都是节点Node。为了解决Hash算法的冲突性,HashMap则使用了节点挂载到数组上。
下面是Node的基本属性和构造方法。
final int hash;
final K key;
V value;
Node<K,V> next;//如果有hash冲突现象,则放到下个节点一起挂载
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
大概长这样:
在使用put(Key,Value)的时候,会先计算出Key的哈希值hashKey,该值就是数组的索引,通过table[hashKey]则快速获得第一个节点。
如果该地方没有节点,则代表是空的,直接插入即可。
如果该地方有节点,查看Key是否相等,如果相等,那么就覆盖掉本节点,如果key不相等,则去找下个节点,并重复该步骤。
如果所有节点的key都不相等,意味着这又是一次hash冲突,则新建节点,挂载到最后。
在使用get(key)的时候,会计算出hashKey,通过table[hashKey]获得节点。
如果是null,则代表目前没有该节点。
如果不是空的,则查看key是否相等,如果相等,那么就把value值返回,如果key不相等,则去找下个节点,并重复该步骤。
如果所有节点的Key都不相等,则代表是null.
推导式分析:
假如现在内部table定长是100,而hash计算结果大都是0~20,也就是基本上所有的元素都在0~20的地方挂载着,20~80的反而寥寥无几,这就是垃圾的hash函数。
作为容器使用的hash函数应该有均匀分布的特性,要能充分利用空间,防止元素聚集,挂载太多导致查找的效率降低。
HashMap内部计算hash时,它的计算策略是和当前数组长度有关的动态hash策略,数组长度不同,键值对的Key计算出的hash也是不同的,目的都是为了保证均匀分布。
HashMap默认长度是16,那如果往里面添加更多的元素,则数组会扩容,每次扩一倍,hash算法将重置,内部元素则依据新hash算法重新插入一个扩容的数组。
扩容时机在于这个参数: static final float DEFAULT_LOAD_FACTOR = 0.75f,当数组内当前元素数量达到数组长度的75%时,则会扩容,重新计算hash.
这意味着DEFAULT_LOAD_FACTOR的值越小,代表着空闲空间很多,但是挂载情况少效率高。值越大,空间利用率高,挂载情况将多。
一般情况下DEFAULT_LOAD_FACTOR为0.5~0.75之间,HashMap默认的时0.75f