HashMap源码解析
简介
hashMap是基于map接口的实现,允许key允许一条为null,value允许多条为null。它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但不保证有序(即插入的顺序),也不保证顺序不随时间变化而变化。hashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
数据结构
hashMap采用 数组 + 链表 + 红黑树的存储结构(链表长度大于8以后,采用红黑树存储)
源码解析
Node()
1 | static class Node<K,V> implements Map.Entry<K,V> { |
变量属性
1 | // 默认的初始容量为16 |
构造方法
1 | public HashMap(int initialCapacity, float loadFactor) { |
tableSizeFor()
1 | /*tableSizeFor(initialCapacity) 方法返回的值是最接近 initialCapacity 的2的幂,若指定初始容量为9,则实际 hashMap 容量为16*/ |
hash()
1 | static final int hash(Object key) { |
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) 高位运算主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。
下面举例说明下,n为table的长度。
put()
1 | public V put(K key, V value) { |
tab[i = (n - 1) & hash]) 抽象为公式int index = hashCode & (length -1),index即表示节点在链表中的位置
get()
1 | public V get(Object key) { |
resize()
1 | final Node<K,V>[] resize() { |
reSize()主要包括三个关键内容:
(1)老table的置为null,方便gc回收释放
(2)新table申请
(3)重新计算记录的hash值,以将键值对插入到新table中
上文中已经提到,table的长度确保是2的n次方,那么有意思的是,每次扩容容量变为原来的两倍,那么一个记录在新table中的位置要么就和原来一样,要么就需要迁移到(oldCap + index)的位置上。
1 | 两个元素A和B,哈希值分别为3和47,在table长度为4的情况下,因为(3) = (11),所以A和B会有两位参与运算来 |