前言 Java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashMap、Hashtable、LinkedHashMap和TreeMap。其中HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。HashMap是一种无序的,且允许null作为键值对的Map集合。该类除了不是同步的,且允许null键值对外,其他都与Hashtable基本一致。 由于Hashtable是遗留类,且并发性不如ConcurrentHashMap,所以在新代码中基本上不使用。HashMap的底层采用 Node数组+链表+红黑树 的存储结构。该类除了没有并发功能外,与 ConcurrentHashMap 类基本一致。
源码解析
数据结构 1 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 class HashMap <K ,V > extends AbstractMap <K ,V > implements Map <K ,V >, Cloneable , Serializable { static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 ; static final int MAXIMUM_CAPACITY = 1 << 30 ; static final float DEFAULT_LOAD_FACTOR = 0.75f ; static final int TREEIFY_THRESHOLD = 8 ; static final int UNTREEIFY_THRESHOLD = 6 ; static final int MIN_TREEIFY_CAPACITY = 64 ; transient Node<K,V>[] table; transient Set<Entry<K,V>> entrySet; transient int size; transient int modCount; int threshold; final float loadFactor; static class Node <K ,V > implements Entry <K ,V > {} static final class TreeNode <K ,V > extends LinkedHashMap .Entry <K ,V > {} }
关于HashMap中的一些属性,我们使用图表来展示。
属性名
默认属性值
含义
DEFAULT_INITIAL_CAPACITY
2^4
在创建HashMap对象时,如果没有指定初始化值,则默认的容量大小为该值
MAXIMUM_CAPACITY
2^30
表示HashMap对象所能包含的最大容量值
DEFAULT_LOAD_FACTOR
0.75f
决定哈希表在达到什么程度时可以扩容。当(负载因子)x(容量)>(Map 大小)时,则调整 Map 大小
TREEIFY_THRESHOLD
8
链表转树的阈值。大于等于8时,转为树结构
UNTREEIFY_THRESHOLD
6
树转链表的阈值。小于等于6时,转为链表结构
MIN_TREEIFY_CAPACITY
64
转换为树结构的最小表容量。当表容量小于该值时,扩容;否则,将对于索引上的链表转为树结构
属性名
含义
Node[] table
内部Node节点数组。用于存储键值对。在首次使用时进行初始化,并在必要时进行扩容。分配时,长度为2的次幂。
Set<Entry> entrySet
Map 中所包含映射的 Set 视图
size
HashMap中实际存在的键值对数量
modCount
记录HashMap内部结构发生变化的次数,主要用于迭代的快速失败。
threshold
HashMap所能容纳的最大数据量的Node(键值对)个数。threshold = length * Load factor。如果Map没有被分配,则此字段表示初始化数组容量。否则当 size > threshold 时,进行扩容。
loadFactor
负载因子。
Node节点 1 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 static class Node <K ,V > implements 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) { Entry<?,?> e = (Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true ; } return false ; } }
Node是HashMap的一个内部类,实现了Map.Entry接口,本质是就是一个映射(键值对)。可以将该类与ConcurrentHashMap中的Node节点类进行对比。
构造函数 1 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 public HashMap (int initialCapacity) { this (initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap () { this .loadFactor = DEFAULT_LOAD_FACTOR; } public HashMap (int initialCapacity, float loadFactor) { if (initialCapacity < 0 ) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this .loadFactor = loadFactor; this .threshold = tableSizeFor(initialCapacity); } public HashMap (Map<? extends K, ? extends V> m) { this .loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false ); }
HashMap类提供了四个构造函数。但是其内部都是对其两个属性:loadFactor 和 threshold 进行赋值。在确认threshold时,使用了 tableSizeFor方法,该方法是获取不小于参数的最小2的次幂。具体实现,在下面给出源码。所以从这里可知,在只用指定容量对HashMap进行初始化时,HashMap的实际容量不一定就是你所指定的值。
tableSizeFor源码 1 2 3 4 5 6 7 8 9 10 static final int tableSizeFor (int cap) { int n = cap - 1 ; n |= n >>> 1 ; n |= n >>> 2 ; n |= n >>> 4 ; n |= n >>> 8 ; n |= n >>> 16 ; return (n < 0 ) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1 ; }
该方法是获取大于指定值cap的最少的2次幂。下图为该方法执行的结果:
cap
tableSizeFor(cap) 的结果
cap
tableSizeFor(cap) 的结果
0
0
5
8
1
1
6
8
2
2
7
8
3
4
8
8
4
4
9
16
put源码 1 2 3 4 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
在该方法的内部,首先通过hash函数获取键值key的哈希值,然后调用 putVal方法添加一个键值对。
hash源码 1 2 3 4 5 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的。这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。
putVal源码 1 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 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; if ((p = tab[i = (n - 1 ) & hash]) == null ) tab[i] = newNode(hash, key, value, null ); else { 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 TreeNode) e = ((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 ; }
该方法是HashMap的核心方法。执行过程可以通过下图来理解:
代码逻辑大致为:
判断键值对数组table是否初始化,否的话,执行resize()进行初始化
根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,添加成功后,执行7步;否则,执行第3步。
判断table[i]的首个元素是否和key一样,如果相同获取对应的 Node节点,然后执行第6步。(通过hashCode和equals()方法判断是否相同);否则,执行第4步。
判断table[i] 是否为树结构,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向第5步
遍历table[i]位置处的链表。如果遍历过程中发现key已经存在则获取对应的 Node 节点,返回跳出循环,执行第6步;否则,将key-value键值组装成Node节点,然后添加到链表的最尾部。添加成功后,判断链表长度是否大于8,大于8的话把链表转换为红黑树。转换完成后,执行第6步。
如果有需要的话,覆盖key对应节点value的值。
判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
resize源码 1 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null ) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0 ; 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 ; } else if (oldThr > 0 ) newCap = oldThr; else { newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int )(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0 ) { float ft = (float )newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float )MAXIMUM_CAPACITY ? (int )ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings ({"rawtypes" ,"unchecked" }) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null ) { 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 { 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; } } } } } return newTab; }
该方法的功能是:对HashMap对象进行扩容。JDK1.8 中的扩容实现逻辑与JDK1.7的实现逻辑有所不同。本文是针对JDK1.8的源码进行分析。 中的相关讲解。再次简单介绍大体流程:
确认HashMap的本次扩容的大小(capacity)及下次扩容的大小(threshold)
创建新容量数组table,然后修改内部属性table的值
判断原键值对数组table是否为空。为空,直接返回新数组;否则,往下执行。
遍历原键值对数组table。
如果原键值对数组table的索引j处链表不空,赋值给e变量,然后往下执行。
如果变量e的next域为空,则直接将变量e添加进新数组的指定索引处。否则,执行下一步
如果变量e为树结构,则通过树操作将树结构中的节点添加到新数组
如果变量e为链表结构,则遍历链表结构,将各个元素添加到新数组。
备注:关于将链表上的节点重新复制到新数组的实现逻辑,JDK1.8中的实现可谓是非常巧妙,跪服了😂😂😂,具体讲解可以详看 Java 8系列之重新认识HashMap 。下面是引用该文章的内容讲解。
1 2 3 4 5 6 下面我们讲解下JDK1.8做了哪些优化。经过观测可以发现,我们使用的是2次幂的 扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置 再移动2次幂的位置。看下图可以明白这句话的意思,n为table的长度, 图(a)表示扩容前的key1和key2两种key确定索引位置的示例, 图(b)表示扩容后key1和key2两种key确定索引位置的示例, 其中hash1是key1对应的哈希与高位运算结果。
1 2 元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色), 因此新的index就会发生这样的变化:
1 2 3 因此,我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值 新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”, 可以看看下图为16扩充为32的resize示意图:
注意区别,JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是JDK1.8不会倒置。
split源码 在对HashMap内部table进行扩展的时候,如果原来索引处的元素为树结构,则会调用split方法对结构进行调整。
1 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 52 53 54 55 56 57 58 59 final void split (HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { TreeNode<K,V> b = this ; TreeNode<K,V> loHead = null , loTail = null ; TreeNode<K,V> hiHead = null , hiTail = null ; int lc = 0 , hc = 0 ; for (TreeNode<K,V> e = b, next; e != null ; e = next) { next = (TreeNode<K,V>)e.next; e.next = null ; if ((e.hash & bit) == 0 ) { if ((e.prev = loTail) == null ) loHead = e; else loTail.next = e; loTail = e; ++lc; } else { if ((e.prev = hiTail) == null ) hiHead = e; else hiTail.next = e; hiTail = e; ++hc; } } if (loHead != null ) { if (lc <= UNTREEIFY_THRESHOLD) tab[index] = loHead.untreeify(map); else { tab[index] = loHead; if (hiHead != null ) loHead.treeify(tab); } } if (hiHead != null ) { if (hc <= UNTREEIFY_THRESHOLD) tab[index + bit] = hiHead.untreeify(map); else { tab[index + bit] = hiHead; if (loHead != null ) hiHead.treeify(tab); } } }
其中逻辑与链表的大体一致。是把原来位置处的一个树结构拆分为两个树结构。在拆分成功后,会判断树节点的个数是否小于等于6,如果是的话,则会调用 untreeify方法将树结构转为链表结构。
untreeify源码 1 2 3 4 5 6 7 8 9 10 11 12 final Node<K,V> untreeify (HashMap<K,V> map) { Node<K,V> hd = null , tl = null ; for (Node<K,V> q = this ; q != null ; q = q.next) { Node<K,V> p = map.replacementNode(q, null ); if (tl == null ) hd = p; else tl.next = p; tl = p; } return hd; }
该方法是:在树结构中节点的个数不大于6的时候,将树结构转为链表结构 。
treeifyBin源码 1 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 final void treeifyBin (Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1 ) & hash]) != null ) { TreeNode<K,V> hd = null , tl = null ; do { TreeNode<K,V> p = replacementTreeNode(e, null ); if (tl == null ) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null ); if ((tab[index] = hd) != null ) hd.treeify(tab); } }
在成功添加后,如果指定位置上的链表节点个数不小于8的时候,则要将链表结构转换为树结构。其中基本逻辑是:将Node类型的节点转换为TreeNode类型的节点,然后在通过TreeNode 类中的 treeify 方法实现树形转换的功能。
treeify源码 1 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 52 53 54 55 56 57 58 final void treeify (Node<K,V>[] tab) { TreeNode<K,V> root = null ; for (TreeNode<K,V> x = this , next; x != null ; x = next) { next = (TreeNode<K,V>)x.next; x.left = x.right = null ; if (root == null ) { x.parent = null ; x.red = false ; root = x; } else { K k = x.key; int h = x.hash; Class<?> kc = null ; for (TreeNode<K,V> p = root;;) { int dir, ph; K pk = p.key; if ((ph = p.hash) > h) dir = -1 ; else if (ph < h) dir = 1 ; else if ((kc == null &&(kc = comparableClassFor(k)) == null ) || (dir = compareComparables(kc, k, pk)) == 0 ) dir = tieBreakOrder(k, pk); TreeNode<K,V> xp = p; if ((p = (dir <= 0 ) ? p.left : p.right) == null ) { x.parent = xp; if (dir <= 0 ) xp.left = x; else xp.right = x; root = balanceInsertion(root, x); break ; } } } } moveRootToFront(tab, root); }
该方法的功能就是将链表结构转换为树结构。整体逻辑就是:遍历链表结构上的所有节点,然后进行组装。关于数结构的相关操作,本文不做讲述,可以看下源码中的注释。关于树结构可以点击学习 。
get源码 1 2 3 4 public V get (Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }
在调用该方法获取指定键值key对应的value值时,首先获取键值key的哈希值,然后通过getNode方法获取对应的Node节点。如果该节点存在,则返回节点的value值;否则,说明键值key对应的节点不存在,返回null。
getNode源码 1 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 final Node<K,V> getNode (int hash, Object key) { Node<K,V>[] tab; 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 TreeNode) return ((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 ; }
该方法是获取键值key对应的Node节点。实现逻辑为:
判断HashMap是否存有键值对,如果有数据且对应的位置 (n - 1) & hash(即键值key的哈希值所在的位置) 上有存储节点,赋值 first节点 = tab[(n - 1) & hash] ,则执行下一步;否则返回null,方法结束。
如果first节点与key一样,则直接返回first节点。
如果first节点为树结构,则通过树的相关查找逻辑查找,返回返回
否则通过遍历链表节点,进行查找。找到返回指定节点,找不到,返回null。
getTreeNode节点 在为树结构的时候,通过该方法进行查找指定值
1 2 3 4 final TreeNode<K,V> getTreeNode (int h, Object k) { return ((parent != null ) ? root() : this ).find(h, k, null ); }
该方法的功能是:从树结构的根节点开始查找元素。如果当前节点不是树的根节点,则首先需要找到树的根节点,然后在进行查找。
root源码 1 2 3 4 5 6 7 final TreeNode<K,V> root () { for (TreeNode<K,V> r = this , p;;) { if ((p = r.parent) == null ) return r; r = p; } }
查找树结构的根节点的条件是:根节点的parent属性为null。所以只需要从当前节点开始向上查找其父节点,如果父节点为null,说明该节点为树结构的根节点。
find源码 1 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 final TreeNode<K,V> find (int h, Object k, Class<?> kc) { TreeNode<K,V> p = this ; do { int ph, dir; K pk; TreeNode<K,V> pl = p.left, pr = p.right, q; if ((ph = p.hash) > h) p = pl; else if (ph < h) p = pr; else if ((pk = p.key) == k || (k != null && k.equals(pk))) return p; else if (pl == null ) p = pr; else if (pr == null ) p = pl; else if ((kc != null || (kc = comparableClassFor(k)) != null ) && (dir = compareComparables(kc, k, pk)) != 0 ) p = (dir < 0 ) ? pl : pr; else if ((q = pr.find(h, k, kc)) != null ) return q; else p = pl; } while (p != null ); return null ; }
该方法就是对树结构进行查找操作。
remove源码 1 2 3 4 5 public V remove (Object key) { Node<K,V> e; return (e = removeNode(hash(key), key, null , false , true )) == null ? null : e.value; }
在计算到键值key对应的哈希值后,调用removeNode方法进行移除。
removeNode源码 1 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 52 53 final Node<K,V> removeNode (int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1 ) & hash]) != null ) { 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 TreeNode) node = ((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 TreeNode) ((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 ; }
在移除节点的时候,首先要找到键值key所对应的Node节点。其内部查找Node节点的代码逻辑与 getNode方法一致。在找到对应的Node节点后,会进行移除操作。如果为树结构,则通过 removeTreeNode 方法进行树移除操作;否则进行节点的相关移除操作。
clear源码 1 2 3 4 5 6 7 8 9 10 public void clear () { Node<K,V>[] tab; modCount++; if ((tab = table) != null && size > 0 ) { size = 0 ; for (int i = 0 ; i < tab.length; ++i) tab[i] = null ; } }
containsValue源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public boolean containsValue (Object value) { Node<K,V>[] tab; V v; if ((tab = table) != null && size > 0 ) { for (int i = 0 ; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null ; e = e.next) { if ((v = e.value) == value || (value != null && value.equals(v))) return true ; } } } return false ; }
由源码可知,在查找指定的value值是否存在时,会整体扫描内部的数据结构,此操作的效率明显是非常低的。
containsKey源码 1 2 3 public boolean containsKey (Object key) { return getNode(hash(key), key) != null ; }
在根据键值key进行查找时,内部会调用 getNode查找,该在上面已分析过,可知该方法比 containsValue 方法效率高的多。
视图展示 keySet源码 1 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 public Set<K> keySet () { Set<K> ks; return (ks = keySet) == null ? (keySet = new KeySet()) : ks; } final class KeySet extends AbstractSet <K > { public final int size () { return size; } public final void clear () { HashMap.this .clear(); } public final Iterator<K> iterator () { return new KeyIterator(); } public final boolean contains (Object o) { return containsKey(o); } public final boolean remove (Object key) { return removeNode(hash(key), key, null , false , true ) != null ; } public final Spliterator<K> spliterator () { return new KeySpliterator<>(HashMap.this , 0 , -1 , 0 , 0 ); } public final void forEach (Consumer<? super K> action) { Node<K,V>[] tab; if (action == null ) throw new NullPointerException(); if (size > 0 && (tab = table) != null ) { int mc = modCount; for (int i = 0 ; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null ; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } } final class KeyIterator extends HashIterator implements Iterator <K > { public final K next () { return nextNode().key; } }
values源码 1 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 public Collection<V> values () { Collection<V> vs; return (vs = values) == null ? (values = new Values()) : vs; } final class Values extends AbstractCollection <V > { public final int size () { return size; } public final void clear () { HashMap.this .clear(); } public final Iterator<V> iterator () { return new ValueIterator(); } public final boolean contains (Object o) { return containsValue(o); } public final Spliterator<V> spliterator () { return new ValueSpliterator<>(HashMap.this , 0 , -1 , 0 , 0 ); } public final void forEach (Consumer<? super V> action) { Node<K,V>[] tab; if (action == null ) throw new NullPointerException(); if (size > 0 && (tab = table) != null ) { int mc = modCount; for (int i = 0 ; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null ; e = e.next) action.accept(e.value); } if (modCount != mc) throw new ConcurrentModificationException(); } } } final class ValueIterator extends HashIterator implements Iterator <V > { public final V next () { return nextNode().value; } }
entrySet源码 1 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 52 53 public Set<Entry<K,V>> entrySet() { Set<Entry<K,V>> es; return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; } final class EntrySet extends AbstractSet <Entry <K ,V >> { public final int size () { return size; } public final void clear () { HashMap.this .clear(); } public final Iterator<Entry<K,V>> iterator() { return new EntryIterator(); } public final boolean contains (Object o) { if (!(o instanceof Map.Entry)) return false ; Entry<?,?> e = (Entry<?,?>) o; Object key = e.getKey(); Node<K,V> candidate = getNode(hash(key), key); return candidate != null && candidate.equals(e); } public final boolean remove (Object o) { if (o instanceof Map.Entry) { Entry<?,?> e = (Entry<?,?>) o; Object key = e.getKey(); Object value = e.getValue(); return removeNode(hash(key), key, value, true , true ) != null ; } return false ; } public final Spliterator<Entry<K,V>> spliterator() { return new EntrySpliterator<>(HashMap.this , 0 , -1 , 0 , 0 ); } public final void forEach (Consumer<? super Entry<K,V>> action) { Node<K,V>[] tab; if (action == null ) throw new NullPointerException(); if (size > 0 && (tab = table) != null ) { int mc = modCount; for (int i = 0 ; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null ; e = e.next) action.accept(e); } if (modCount != mc) throw new ConcurrentModificationException(); } } } final class EntryIterator extends HashIterator implements Iterator <Entry <K ,V >> { public final Entry<K,V> next () { return nextNode(); } }
从上面的视图相关代码中,可知无论是哪个迭代器,其内部在遍历的时候都会调用 nextNode方法获取下个节点,然后返回指定值。
好了,HashMap相关学习到此结束,总之,跪服了😂😂。多亏了Java 8系列之重新认识HashMap 这个文章,不然JDK1.8的优化,不会了解的这么详细😂😂😂
参考文章 Java 8系列之重新认识HashMap
Java Map 集合类简介
HashMap 源码详细分析(JDK1.8)