Map系列之HashMap(源码基于java8)
HashMap
是我们最常用的map
实现之一,这篇文章将会介绍HashMap
内部是如何工作的,以及内部的数据结构是怎样的
一、数据结构简图
二、源码解析
首先看下Map
接口里常用的几个方法:
代码块11 2 3 4
| V put(K key, V value); V get(Object key); V remove(Object key); boolean containsKey(Object key);
|
上面是常用的主要操作方法,下面来看下map
的基本存储单位Entry
:
代码块21 2 3 4 5 6 7 8 9 10 11 12 13
| interface Entry<K,V> { K getKey();
V getValue();
V setValue(V value); boolean equals(Object o);
int hashCode(); }
|
然后我们来看下HashMap
里对该接口的实现:
代码块31 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; }
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); }
public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; }
public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry e = (Map.Entry)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
|
我们再来看看hash值的计算,在哈希表中,哈希值取决了散列度,最终插入的数据会分布到哪个数组下标下,hash值起着至关重要的作用:
代码块41 2 3 4
| static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
|
下面我们来看看具体插入数据时做的操作,具体解释已经加上注释:
代码块51 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { HashMap.Node<K,V>[] tab;
HashMap.Node<K,V> p;
int n, i;
if ((tab = table) == null || (n = tab.length) == 0) { n = (tab = resize()).length; } if ((p = tab[i = (n - 1) & hash]) == null) { tab[i] = newNode(hash, key, value, null); } else { HashMap.Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) { e = p; } else if (p instanceof HashMap.TreeNode){ e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); } else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { break; } p = e; } } if (e != null) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) { resize(); } afterNodeInsertion(evict); return null; }
|
由于java8
做了根据元素数量,转换成红黑树结构的优化处理,所以上述代码中会掺杂一些相关的代码,这里先不用关心,我们按照最基本的哈希表结构来看就行,下一讲将会分析红黑树结构。
我们接下来来看下get
方法:
代码块61 2 3 4
| public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }
|
然后getNode
方法:
代码块71 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| final HashMap.Node<K,V> getNode(int hash, Object key) { HashMap.Node<K,V>[] tab; HashMap.Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))){ return first; } if ((e = first.next) != null) { if (first instanceof HashMap.TreeNode) { return ((HashMap.TreeNode<K,V>)first).getTreeNode(hash, key); } do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){ return e; } } while ((e = e.next) != null); } } return null; }
|
ok,上面说完了put
和get
,现在我们来看下remove
,也是先抛开红黑树不谈,只看链表部分,会很容易:
代码块81 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public V remove(Object key) { HashMap.Node<K, V> e; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; }
final HashMap.Node<K, V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { HashMap.Node<K, V>[] tab; HashMap.Node<K, V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { HashMap.Node<K, V> node = null, e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) { node = p; } else if ((e = p.next) != null) { if (p instanceof HashMap.TreeNode) { node = ((HashMap.TreeNode<K, V>) p).getTreeNode(hash, key); } else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof HashMap.TreeNode) { ((HashMap.TreeNode<K, V>) node).removeTreeNode(this, tab, movable); } else if (node == p) { tab[index] = node.next; } else { p.next = node.next; } ++modCount; --size; afterNodeRemoval(node); return node; } } return null; }
|
好了,目前基本上把重要的一些操作给介绍完了,现在再看下containsKay
这个方法,这个方法极度简单,直接调用getNode
方法判空即可:
代码块91 2 3
| public boolean containsKey(Object key) { return getNode(hash(key), key) != null; }
|
本篇的侧重点在于HashMap
在使用纯链表时的插入、移除、查找方式,下一篇将会介绍HashMap
如何扩容数组、以及在启用红黑树结构下,会如何做插入、移除、查找这几种操作方式。