赞
踩
面试高频问题,这里提供下参考。
认真阅读了下HashMap的实现方式,也参考了网上别人的一些解析,个人觉得还是有些东西想说。网上有的文章名字为HashMap源码解析,实际上就是给它里面的一些方法加上一些注释而已,有不少都是这样的。
我自己看源码的时候,发现不是别人不想解析,而是它的实现真的需要亲自研读,多理顺几遍才知道怎么回事。
我在这里解析的文字描述也较多,不管谁的解析,自己也都要看一下JDK源码的具体实现,我们仅提供参考而已。
源码不太方便看,先说明一下我的阅读思路。
我觉得HashMap的实现方式不够好,关键的几个方法里面包含的情况太多了,阅读起来是有难度的,而写程序的目的之一不就是让其他开发者阅读吗?一个方法内部做了太多的事情,违反了代码整洁的规则,一个函数做要尽量少的事情。
之前稍微介绍了一些HashMap的特性,HashMap初探。这里接着深入。
先挑最简单的说...
- public V get(Object key) {
- Node<K,V> e;
- return (e = getNode(hash(key), key)) == null ? null : e.value;
- }
- // hash值为hash(key),key
- final Node<K,V> getNode(int hash, Object key) {
- Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
- //table不为空,并且tab[(n-1) & hash] != null的时候。
- if ((tab = table) != null && (n = tab.length) > 0 &&
- (first = tab[(n - 1) & hash]) != null) {
-
- //判断取出Node的hash值是否相等。key值相等,那么直接返回。
- //想一想什么情况下,if语句不成立?
- if (first.hash == hash && // always check first node
- ((k = first.key) == key || (key != null && key.equals(k))))
- return first;
-
- //也就是取出的第一个Node的hash值与key计算的hash不等。
- if ((e = first.next) != null) {
- if (first instanceof TreeNode)
-
- //从树中取节点。
- return ((TreeNode<K,V>)first).getTreeNode(hash, key);
-
- do {
- //判断hash值与key值是否相等,一直判断到相等或到节点末端为止。
- if (e.hash == hash &&
- ((k = e.key) == key || (key != null && key.equals(k))))
- return e;
- } while ((e = e.next) != null);
- }
- }
- return null;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这个中间涉及的逻辑多一些,方法需要分不同的步骤看。
思路:
- 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;
-
- //如果table为空,就重新创建table
- if ((tab = table) == null || (n = tab.length) == 0)
- n = (tab = resize()).length;
- // 如果tab[(n-1) & hash]为空的话,就在tab[(n-1) & hash]位置存储节点。
- // newNode = new Node<>(hash, key, value, next);
- if ((p = tab[i = (n - 1) & hash]) == null)
- tab[i] = newNode(hash, key, value, null);
- else {
-
- //当tab[(n-1)&hash]位置已经存在Node的时候。
- Node<K,V> e; K k;
-
- //如果已经存在的Node与即将要存的key值一样
- // e为存在的Node
- if (p.hash == hash &&
- ((k = p.key) == key || (key != null && key.equals(k))))
- e = p;
- else if (p instanceof TreeNode)
- e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
- else {
- //不满足以上的情况,一直把Node往后插入。
- //如果插入的节点数量多于TREEIFY_THRESHOLD-1个,变为树形节点
- for (int binCount = 0; ; ++binCount) {
- if ((e = p.next) == null) {
- p.next = newNode(hash, key, value, null);
- if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
- treeifyBin(tab, hash);
- break;
- }
- //如果再遍历的时候,发现key值相同的时候,就跳出循环。e = p.next,
- // 这时已经记录e的Node值了
- if (e.hash == hash &&
- ((k = e.key) == key || (key != null && key.equals(k))))
- break;
- p = e;
- }
- }
-
- // 存在对应的Node时
- if (e != null) { // existing mapping for key
- V oldValue = e.value;
- if (!onlyIfAbsent || oldValue == null)
- e.value = value;
- afterNodeAccess(e);
- return oldValue;
- }
- }
-
- // HashMap内部修改的初始
- ++modCount;
-
- //如果存储的节点数,大于临界值,重新分配大小
- if (++size > threshold)
- resize();
-
- //抽象方法,当节点执行插入操作的时候如何处理
- afterNodeInsertion(evict);
- return null;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
一般不发生碰撞的时候,相对简单,数据量较小的情况下。
- final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
- boolean evict) {
- Node<K,V>[] tab; Node<K,V> p; int n, i;
- if ((tab = table) == null || (n = tab.length) == 0)
- n = (tab = resize()).length;
-
- // 留意 i = (n-1)&hash,所以取的时候也这样取
- // newNode = new Node<>(hash, key, value, next);
- if ((p = tab[i = (n - 1) & hash]) == null)
- tab[i] = newNode(hash, key, value, null);
- ++modCount;
- if (++size > threshold)
- resize();
- afterNodeInsertion(evict);
- return null;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
发生碰撞后,有个红黑树的处理,因为红黑树相对知识点较多,下次单独详细解释。这里可以参考以下,从JDK源码研究红黑树。我解释下关于碰撞冲的循环。
- for (int binCount = 0; ; ++binCount) {
- // p.next为null的时候,走到了链表的末端,然后新建一个节点,如果链表的长度太长,转换为树存储。
- if ((e = p.next) == null) {
- p.next = newNode(hash, key, value, null);
- if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
- treeifyBin(tab, hash);
- break;
- }
-
- // 如果链表中存在于要put的key值相同的时候,存储key值,也就是e ,(e = p.next)。
- if (e.hash == hash &&
- ((k = e.key) == key || (key != null && key.equals(k))))
- break;
- p = e;
- }
红黑树的部分,我们下次单独解析
这个涉及的内容,有不少线需要捋一捋。首先看申明时候会resize()。它们都在调用put的时候执行的。
- if ((tab = table) == null || (n = tab.length) == 0)
- n = (tab = resize()).length;
- if (++size > threshold)
- resize();
- final Node<K,V>[] resize() {
- Node<K,V>[] oldTab = table;
-
- // 之前的容量可能为0或者为之前的大小
- // threshold可能为null或者为2的n次方
- int oldCap = (oldTab == null) ? 0 : oldTab.length;
- int oldThr = threshold;
-
- //新的容量,新的临界目前都为0
- int newCap, newThr = 0;
-
- // 第二次resize的时候。
- if (oldCap > 0) {
- if (oldCap >= MAXIMUM_CAPACITY) {
- threshold = Integer.MAX_VALUE;
- return oldTab;
- }
- else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
- oldCap >= DEFAULT_INITIAL_CAPACITY)
- newThr = oldThr << 1; // double threshold
- }
- //第一次resize()的时候,初始化的操作。
- else if (oldThr > 0) // initial capacity was placed in threshold
- newCap = oldThr;
- else { // zero initial threshold signifies using defaults
- newCap = DEFAULT_INITIAL_CAPACITY;
- newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
- }
-
- //第一次resize(),会进入
- if (newThr == 0) {
- //负载因子 * 初始容量
- float ft = (float)newCap * loadFactor;
-
- //保证临界值不超过最大值。
- newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
- (int)ft : Integer.MAX_VALUE);
- }
-
- //真正初始化的操作,新建newCap个数组,临界值初始化。
- threshold = newThr;
- @SuppressWarnings({"rawtypes","unchecked"})
- Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
- table = newTab;
-
- // 非 第一次reizie()时
- if (oldTab != null) {
- for (int j = 0; j < oldCap; ++j) {
- Node<K,V> e;
- if ((e = oldTab[j]) != null) {
-
- // 重新计算了一次hash
- oldTab[j] = null;
- if (e.next == null)
- newTab[e.hash & (newCap - 1)] = e;
-
- else if (e instanceof TreeNode)
- ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
- // 如果e.next != null 存在hash的Node 链子
- else { // preserve order
- Node<K,V> loHead = null, loTail = null;
- Node<K,V> hiHead = null, hiTail = null;
- Node<K,V> next;
-
- do {
- next = e.next;
- // 原索引
- if ((e.hash & oldCap) == 0) {
- if (loTail == null)
- loHead = e;
- else
- loTail.next = e;
- loTail = e;
- }
- // 原索引+ oldCap
- else {
- if (hiTail == null)
- hiHead = e;
- else
- hiTail.next = e;
- hiTail = e;
- }
- } while ((e = next) != null);
-
- if (loTail != null) {
- loTail.next = null;
- newTab[j] = loHead;
- }
- if (hiTail != null) {
- hiTail.next = null;
- newTab[j + oldCap] = hiHead;
- }
- }
- }
- }
- }
- return newTab;
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
如果是第一次resize,我们抽出来会执行到的语句。
- final Node<K,V>[] resize() {
- //第一次的时候table为null
- Node<K,V>[] oldTab = table;
- // oldCap 为 0,threshod为null
- int oldCap = (oldTab == null) ? 0 : oldTab.length;
- int oldThr = threshold;
-
- int newCap, newThr = 0;
-
- // 不会走
- if (oldCap > 0) {
- ....
- // 从这里执行
- else { // zero initial threshold signifies using defaults
- newCap = DEFAULT_INITIAL_CAPACITY;
- newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
- }
-
- threshold = newThr;
- @SuppressWarnings({"rawtypes","unchecked"})
- Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
- table = newTab;
- return newTab;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
第二次及后续的resize执行流程
- final Node<K,V>[] resize() {
- Node<K,V>[] oldTab = table;
- // oldCap为当前table的长度, oldThr为上次的table临界值
- int oldCap = (oldTab == null) ? 0 : oldTab.length;
- int oldThr = threshold;
-
- int newCap, newThr = 0;
- if (oldCap > 0) {
- // 这个if语句保证容量不超过hashmap的容量上限值。
- if (oldCap >= MAXIMUM_CAPACITY) {
- threshold = Integer.MAX_VALUE;
- return oldTab;
- }
- // 如果扩容之后,不超过容量上限,
- // 那么表的大小加倍。临界值加倍
- else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
- oldCap >= DEFAULT_INITIAL_CAPACITY)
- newThr = oldThr << 1; // double threshold
- }
-
- threshold = newThr;
- @SuppressWarnings({"rawtypes","unchecked"})
- Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
- table = newTab;
-
- //第二次扩容的时候,对上次的table如何处理。
- if (oldTab != null) {
-
- // 遍历之前的table,重新hash排序
- for (int j = 0; j < oldCap; ++j) {
- Node<K,V> e;
- //只对存在的索引操作
- if ((e = oldTab[j]) != null) {
- //销毁当前索引的内容
- oldTab[j] = null;
- //重新计算位置并赋值
- if (e.next == null)
- newTab[e.hash & (newCap - 1)] = e;
- //树的操作,下次再说
- else if (e instanceof TreeNode)
- ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
-
- //链表的操作
- else { // preserve order
- Node<K,V> loHead = null, loTail = null;
- Node<K,V> hiHead = null, hiTail = null;
- Node<K,V> next;
- do {
- next = e.next;
- // oldCap 为 16也就是 10000,
- //oldCap为16的倍数,这里是hash值为低数字的时候
-
- if ((e.hash & oldCap) == 0) {
- //第一次
- if (loTail == null)
- loHead = e;
- //计算新的next
- else
- loTail.next = e;
- loTail = e;
- }
- //同理
- else {
- if (hiTail == null)
- hiHead = e;
- else
- hiTail.next = e;
- hiTail = e;
- }
- } while ((e = next) != null);
-
- if (loTail != null) {
- loTail.next = null;
- newTab[j] = loHead;
- }
- // 加上原本的偏移量
- if (hiTail != null) {
- hiTail.next = null;
- newTab[j + oldCap] = hiHead;
- }
- }
- }
- }
- }
- return newTab;
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
resize中对有碰撞的链表的操作写的很有意思,再叙述一下。在重新分配索引的时候,有重新组建链表的操作。
举个比较夸张的例子,读者就明白了。
这个图说的是,当hashmap的表大小为2扩充到4的时候,原本挂载在1位置的链表,重新分配之后的样子。
- Node<K,V> loHead = null, loTail = null;
- Node<K,V> hiHead = null, hiTail = null;
- Node<K,V> next;
- do {
- next = e.next;
- if ((e.hash & oldCap) == 0) {
- if (loTail == null)
- loHead = e;
- else
- loTail.next = e;
- loTail = e;
- }
- else {
- if (hiTail == null)
- hiHead = e;
- else
- hiTail.next = e;
- hiTail = e;
- }
- } while ((e = next) != null);
- if (loTail != null) {
- loTail.next = null;
- newTab[j] = loHead;
- }
- if (hiTail != null) {
- hiTail.next = null;
- newTab[j + oldCap] = hiHead;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
篇幅有限,我这里仅仅介绍了get方法,put方法,resize方法的具体原理,文章就已经非常长了,不利于阅读。
下次再补充一下HashMap的hash方法原理,其余的相关注意事项。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。