HashMap的源码学习介绍

本文是对HashMap的源码put方法分析:

                 HashMap在1.8之前是数组 和链表结构,在1.8时对其进行了改造,虽然也是基于数组和链表结构,但是在多次hash碰撞后会将链表结构转为红黑树结构。

以下是1.7的put方法的源码:

 public V put(K key, V value) {
if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }

  我们在看下1.8的put方法:

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
/*判断数组是否为null或者长度是否为0,HashMap空参构造方法是不进行初始化初始长度和扩容因子的,在第一次put的时候才进行长度和扩容的初始化.
resize()方法是进行扩容的函数
*/
if ((tab = table) == null || (n = tab.length) == 0)
/*注意扩容的时候,新的桶的位置重新计算、这样在取得时候才能准确根据(n - 1) & hash这个一致,准确的取出数据。
*
* */
n = (tab = resize()).length;
/*
* 这是判断数组的某个位置的是否为null,为null则加入链表的初始节点。
*
* */
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
/*
首个位置的键与新插入的键相等
* 数组的某个位置不为空,①如果hash值相等,②如果key是基本类型用==比较,否则用key != null && key.equals(k)来比较
*如果①和②都满足,这这是存在的数据,直接替换,替换方法在下面的
* if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
注意:当然Hash算法并不完美,有可能两个不同的原始值在经过哈希运算后得到同样的结果,这样就是哈希碰撞。
* */
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
/*首个位置是红黑树的节点
*如果哈希表发生了哈希碰撞,冲突的节点会插入到以table[i]为链表头的尾部。如果该链表长度超过8,会转换成红黑树。因此table[i]既可能是链表头,也可能是红黑树的根部。
如果tablei是红黑树的节点,说明该hash值冲突的节点冲过了8个,将新的键值对插入红黑树。
*
* */
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
/*首个位置是链表节点
*这段代码将原数组中的元素插入到新数组中,具体表现为:
@1 将非链表非红黑树的节点计算新位置后重新插入新数组。
@2 将原红黑树插入新的数组
@3 将原链表插入新的数组
这里要注意的是,扩容后(n-1)的值在高一位多了1,因此原来的链表和红黑树的节点的位置可能出现高一位多了1,
所以不能简单的直接将头节点移动到新数组,需要重新计算位置。而哈希值不需要重新计算,
所以这可能就是** (n-1)&hash **这个算法的好处吧。
*
*
* */
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
/*为什么在hash碰撞大于8个的时候使用红黑树
* 因为红黑树需要进行左旋,右旋操作, 而单链表不需要,
以下都是单链表与红黑树结构对比。
如果元素小于8个,查询成本高,新增成本低
如果元素大于8个,查询成本低,新增成本高
*
* */
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}

++modCount;
//这是进行扩容操作
    if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
 

第一次写分享,很多东西写不好,请大家见谅,有什么不足的请多多指教,希望大家能有收获,谢谢。 

本文参考:https://www.jianshu.com/p/df4a907ef4ef

posted @ 2018-12-11 19:22  不要在意远方多远  阅读(155)  评论(0)    收藏  举报