本篇博客主要分析 jdk1.7 中的 HashMap 的 put() 方法。接下来是几点说明:算法
jdk1.7 中 HashMap 采用的是数组 + 链表的数据结构(ps:若是数组和链表是什么东西,不建议往下看),以下图所示:数组
当 put 一个元素时, 会先计算出元素在数组中的所在位置, 若是那个位置已经有元素存在, 这时就发生了哈希冲突(或叫哈希碰撞)。至于发生哈希冲突后, 稍后详解。安全
注意: 这张图很重要, 下面会反复提到数据结构
HashMap 源码中的数组和链表如何定义的?学习
链表中的节点定义为 Entry<K,V>, 详情能够看下面的代码段(最好结合上面的图)。this
// 链表中的节点 static class Entry<K,V> implements Map.Entry<K,V> { final K key; // 存放 键 V value; // 存放 值 Entry<K,V> next;// 指向链表中的下一个节点 int hash;// 节点对应的hash值 // 此处省略 set/get 等方法 } // 数组 : 默认为空数组 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
这里插一句:线程
好比上面的最后一行代码,里面有一个 transient 关键字, 它的的用法和含义咱们暂时不用了解,着急的话请自行学习,后续的文章可能也会补充,本文咱们只讲流程,它并不会妨碍咱们。 说这句话是由于我以前看的时候,就想把每一处都了解清楚,从而致使耗费了不少时间,最后发现脑子里一团浆糊, 这里再次强调 按部就班。code
由数组和链表的定义,咱们能得出什么?blog
数组中能够存放一个 Entry,也能够存放一个 Entry 链。(结合上面的图)索引
HashMap 中的哈希算法?
把 put 方法传入的 key,通过算法计算,获得一个 int 类型的数字并返回。
key 为 null 的元素放在哪?
数组(table[])中索引为 0 的位置, (再次结合上面的图)
这里说几个 put 方法中用到的几个变量。
// 一个大小为0 的空数组 static final Entry<?,?>[] EMPTY_TABLE = {}; // 存放元素的数组,默认指向上面的空数组 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
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; }
public V put(K key, V value) { // 若是容器为空, 就初始化容器 if (table == EMPTY_TABLE) { inflateTable(threshold); } // 若是 key 为null,放置在第 0 个 桶的位置 if (key == null) return putForNullKey(value); // 根据 key 计算 hash值 int hash = hash(key); // 根据 hash 值计算出应该存放到数组中的哪一个位置(暂时命名为位置 A) int i = indexFor(hash, table.length); // 判断位置 A 是否已经被占据,若是是,就遍历该位置上的链表 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // 若是链表中知足如下条件(命名为条件1)的 Entry ,就覆盖掉它 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++;// 略过,不作分析 // 若是位置 A上是空的 // 或者位置 A 中的链表不存在知足条件1的 Entry,就建立一个Entry 放置在位置A, 充当链表表头。 addEntry(hash, key, value, i); return null; }
这里咱们就看下完整的流程图是什么样的。
上面不少细节都没讲, 好比:hash 算法是怎么实现的,为何? HashMap 的扩容机制,扩容因子为何是 0.75,为何是两倍扩容,甚至其余更难的点,HashMap 为何是非线程安全的等等。 上面这些问题是我学习 HashMap 源码时遇到的问题,基本看到一个方法就想点进去看细节,最后发现没头没尾的。而本文只是先描述下 put 方法的大体执行流程,以后会逐个击破上面提到的问题, 按部就班。