首页 > 技术文章 > HashMap源码分析

bhbcsc 2017-07-11 11:03 原文

HashMap简述

在JDK中,HashMap是存储键值对用的比较多的一个类。

其基于哈希散列表计算位置来达到键不重复存储。

其内部数据结构是数组(散列桶)+链表+红黑树,

数组是基础存储,存储位置为计算出来的hash值和数组长度减一相与,而数组长度一直都为2的整数幂。

链表是遇到哈希碰撞时,即数组同一下标要存放第二个值的时候,会在原值后面链接上下一个键值对。

红黑树是在链表过长(默认大于8)时进行的结构重组,将链表转换为红黑树,加快搜索效率。

HashMap的声明

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

扩展Map接口代表这个类是存储键值对的数据结构。

HashMap的域

在以往的版本中,是用Entry<K,V>来表示一个键值对结点的,而后来引入了红黑树表示的节点(TreeNode),就产生了新的表示方法(Node)。

 1 transient Node<K,V>[] table; //这是HashMap的域
 2 
 3 //内部类
 4 static class Node<K,V> implements Map.Entry<K,V> {
 5         final int hash;
 6         final K key;
 7         V value;
 8         Node<K,V> next;
 9 
10         //下面还有一些方法,如geKeyt、setValue、toString
11 }
12 
13 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> 
14 
15 //下面这个是LinkedHashMap.Entry<K,V> 的声明
16 //TreeNode是Node的子类
17 //具体的实现不在这里描述,可以参考红黑树或者TreeMap<K,V>
18 static class Entry<K,V> extends HashMap.Node<K,V> 
Node

 

 1 //用作缓存entrySet()函数的结果
 2 transient Set<Map.Entry<K,V>> entrySet;
 3 
 4 //当前存放键值对的个数
 5 transient int size;
 6 
 7 //加载因子
 8 final float loadFactor;
 9 
10 //capacity(键值对数组table的大小)*loadFactor
11 //当size>threshold会进行一次扩容(resize)
12 int threshold;
13 
14 //若初始化没指定时各个域的默认值
15 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
16 
17 static final float DEFAULT_LOAD_FACTOR = 0.75f;
18 
19 //一个HashMap capacity的最大容量
20 static final int MAXIMUM_CAPACITY = 1 << 30;
21 
22 
23 //链表和红黑树互相转换时参照的值
24 static final int TREEIFY_THRESHOLD = 8;
25 
26 static final int UNTREEIFY_THRESHOLD = 6;
27 
28 //使用红黑树时最小的capacity值
29  static final int MIN_TREEIFY_CAPACITY = 64;
fields

 HashMap的构造方法

 1 public HashMap(int initialCapacity, float loadFactor) {
 2         if (initialCapacity < 0)
 3             throw new IllegalArgumentException("Illegal initial capacity: " +
 4                                                initialCapacity);
 5         if (initialCapacity > MAXIMUM_CAPACITY)
 6             initialCapacity = MAXIMUM_CAPACITY;
 7         if (loadFactor <= 0 || Float.isNaN(loadFactor))
 8             throw new IllegalArgumentException("Illegal load factor: " +
 9                                                loadFactor);
10         this.loadFactor = loadFactor;
11         this.threshold = tableSizeFor(initialCapacity);
12     }
13 
14     public HashMap(int initialCapacity) {
15         this(initialCapacity, DEFAULT_LOAD_FACTOR);
16     }
17 
18     public HashMap() {
19         this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
20     }
21 
22     public HashMap(Map<? extends K, ? extends V> m) {
23         this.loadFactor = DEFAULT_LOAD_FACTOR;
24         putMapEntries(m, false);
25     }
constructors

除了参数为Map的构造方法,其余的都是参数检测和默认赋值,

另外值得一提的是,传入initCapacity的构造方法都会先将其放入threshold中,

可以发现构造方法中并没有构建任何数据结构,这些会在第一次put中才会生成。

参数为Map的构造方法调用了另外的核心方法,后面再详细叙述。

HashMap的关键方法

介绍增删改查之前,先介绍有关size的方法,也就是table大小的设定。

resize方法除了初始化会使用,在扩容的时候也会使用,但也同样保证了是2次幂。

 

  1 final Node<K,V>[] resize() {
  2         Node<K,V>[] oldTab = table;
  3     //如果table为空,oldCap设为0,否则设为table的长度
  4         int oldCap = (oldTab == null) ? 0 : oldTab.length;
  5         int oldThr = threshold;
  6         int newCap, newThr = 0;
  7     //1:table不为空(扩容)
  8         if (oldCap > 0) {
  9             //1.1:table超过最大值,设 treshold为int最大值
 10         if (oldCap >= MAXIMUM_CAPACITY) {
 11                 threshold = Integer.MAX_VALUE;
 12                 return oldTab;
 13             }
 14         //设newCap为oldCap的两倍
 15         //1.2:如果newCap小于最大长度且oldCap大于初始长度
 16             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
 17                      oldCap >= DEFAULT_INITIAL_CAPACITY)
 18                 newThr = oldThr << 1; // double threshold
 19         }
 20     //2:table为空而threshold不为空,那初始的capacity就为threshold
 21         else if (oldThr > 0) // initial capacity was placed in threshold
 22             newCap = oldThr;
 23         else {//3:没有设置初始容量,就设为默认容量和默认threshold
 24             newCap = DEFAULT_INITIAL_CAPACITY;
 25             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
 26         }
 27     //如果是上述1.1或者2情况时,尚未指定newThr,则计算
 28         if (newThr == 0) {
 29             float ft = (float)newCap * loadFactor;
 30         //计算是否超过最大值,超过就赋予int最大值
 31             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
 32                       (int)ft : Integer.MAX_VALUE);
 33         }
 34         threshold = newThr;
 35     //生成初始化后或扩容后的table
 36         @SuppressWarnings({"rawtypes","unchecked"})
 37             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
 38         table = newTab;
 39     
 40         if (oldTab != null) {
 41             //遍历旧值传入新table
 42         for (int j = 0; j < oldCap; ++j) {
 43                 Node<K,V> e;//e保存旧结点,旧结点设为null等待回收
 44                 if ((e = oldTab[j]) != null) {
 45                     oldTab[j] = null;
 46             //某结点下一个值为空(单一结点),直接赋值
 47                     if (e.next == null)
 48                         newTab[e.hash & (newCap - 1)] = e;
 49                     //某结点为TreeNode,用TreeNode方法赋值
 50             else if (e instanceof TreeNode)
 51                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
 52                     //某结点是一个链表
 53             //发现扩容后同一hash值的节点要么会在原数组索引位置
 54             //要么会在原数组索引+原数组长度位置
 55             //因为扩容总是扩展2的倍数
 56             else { // preserve order
 57                         Node<K,V> loHead = null, loTail = null;//原位置
 58                         Node<K,V> hiHead = null, hiTail = null;//扩展位置(高位置)
 59                         Node<K,V> next;
 60                         do {
 61                             next = e.next;
 62                 //相与判断是否hash值仍在原索引位置
 63                             if ((e.hash & oldCap) == 0) {
 64                                 if (loTail == null)
 65                                     loHead = e;
 66                                 else
 67                                     loTail.next = e;
 68                                 loTail = e;
 69                             }
 70                             else {
 71                                 if (hiTail == null)
 72                                     hiHead = e;
 73                                 else
 74                                     hiTail.next = e;
 75                                 hiTail = e;
 76                             }
 77                         } while ((e = next) != null);
 78             //原位置连接
 79                         if (loTail != null) {
 80                             loTail.next = null;
 81                             newTab[j] = loHead;
 82                         }
 83             //高位置连接
 84                         if (hiTail != null) {
 85                             hiTail.next = null;
 86                             newTab[j + oldCap] = hiHead;
 87                         }
 88                     }
 89                 }
 90             }
 91         }
 92         return newTab;
 93     }
 94 
 95 
 96 //使cap设为大于传入cap最小的2次幂
 97 static final int tableSizeFor(int cap) {
 98     //cap - 1为了防止传入cap为2的整数幂,经过计算后会变为原来的2倍
 99         int n = cap - 1;
100         n |= n >>> 1;
101         n |= n >>> 2;
102         n |= n >>> 4;
103         n |= n >>> 8;
104         n |= n >>> 16;
105     //上面操作使得n最高位开始后面全部位都为1
106     //计算后n小于0(传入参数为0或者为负数)返回1
107     //否则如果计算后大于最大cap返回最大
108     //否则返回n+1,即n最高位前一位为1后面全部为0
109     //即是大于原参最小的2次幂
110         return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
111     }
size

 

 1 public V put(K key, V value) {
 2         return putVal(hash(key), key, value, false, true);
 3     }
 4 
 5 //onIfAbsent为true时表示只有在该Key值在Map中不存在时才插入
 6 //evict表示Map为初始化阶段,仅与afterNodeInsertion方法相关联,
 7 //在HashMap中,afterNodeInsertion方法体为空,可以无视
 8 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 9                    boolean evict) {
10         Node<K,V>[] tab; Node<K,V> p; int n, i;
11         //当table为空(第一次插入)时,调用resize方法。
12         if ((tab = table) == null || (n = tab.length) == 0)
13             n = (tab = resize()).length;
14         //如果插入的节点hash值所占table的位置没有元素,则新建一个节点插入
15         if ((p = tab[i = (n - 1) & hash]) == null)
16             tab[i] = newNode(hash, key, value, null);
17         //插入点有元素(哈希碰撞)
18         else {
19             Node<K,V> e; K k;
20             //想插入的和原位置的hash值和key都相等时,将原节点替换为新节点
21             if (p.hash == hash &&
22                 ((k = p.key) == key || (key != null && key.equals(k))))
23                 e = p;
24             //原节点是一个红黑树节点,用红黑树的插入方法
25             else if (p instanceof TreeNode)
26                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
27             //否则(有链表存在)
28             else {
29                 //遍历链表,用binCount记录遍历链表长度
30                 for (int binCount = 0; ; ++binCount) {
31                 //原节点下一个节点为空,直接插入(将原节点next设为该节点)
32                 if ((e = p.next) == null) {
33                         p.next = newNode(hash, key, value, null);
34                         //插入后发现链表长度大于要转换为红黑树的临界值,就转换为红黑树
35                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
36                             treeifyBin(tab, hash);
37                         break;
38                     }
39                     //如果遍历到链表中一节点hash值和key都与要插入的节点相等,就替换该节点
40                     if (e.hash == hash &&
41                         ((k = e.key) == key || (key != null && key.equals(k))))
42                         break;
43                     p = e;
44                 }
45             }
46             //如果插入的地方是有原值的,就返回原来的值
47             if (e != null) { // existing mapping for key
48                 V oldValue = e.value;
49                 if (!onlyIfAbsent || oldValue == null)
50                     e.value = value;
51                 afterNodeAccess(e);
52                 return oldValue;
53             }
54         }
55         ++modCount;
56         //如果插入后size超过threshold,就进行扩容
57         if (++size > threshold)
58             resize();
59         afterNodeInsertion(evict);
60         return null;
61     }        
put

 

 1 public void putAll(Map<? extends K, ? extends V> m) {
 2         putMapEntries(m, true);
 3     }
 4  
 5 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
 6         int s = m.size();
 7         if (s > 0) {
 8             //被插入的为null
 9         if (table == null) { // pre-size
10                 //根据size设置threshold
11         float ft = ((float)s / loadFactor) + 1.0F;
12                 int t = ((ft < (float)MAXIMUM_CAPACITY) ?
13                          (int)ft : MAXIMUM_CAPACITY);
14                 if (t > threshold)
15                     threshold = tableSizeFor(t);
16             }
17         //发现要扩容
18             else if (s > threshold)
19                 resize();
20             //设置好后遍历插入
21         for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
22                 K key = e.getKey();
23                 V value = e.getValue();
24                 putVal(hash(key), key, value, false, evict);
25             }
26         }
27     }
putAll

 

 1 public V get(Object key) {
 2         Node<K,V> e;
 3         return (e = getNode(hash(key), key)) == null ? null : e.value;
 4     }
 5 
 6     final Node<K,V> getNode(int hash, Object key) {
 7         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
 8     //判断table不为null且table有长度且hash值对应table中索引内有数据才进入
 9         if ((tab = table) != null && (n = tab.length) > 0 &&
10             (first = tab[(n - 1) & hash]) != null) {
11             //判断该节点是否为所求(没产生哈希碰撞)
12         if (first.hash == hash && // always check first node
13                 ((k = first.key) == key || (key != null && key.equals(k))))
14                 return first;
15             //产生了哈希碰撞
16         if ((e = first.next) != null) {
17                 //节点为TreeNode,用treeNode方法取出
18         if (first instanceof TreeNode)
19                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);
20         //节点为链表,循环遍历next
21         do {
22                     if (e.hash == hash &&
23                         ((k = e.key) == key || (key != null && key.equals(k))))
24                         return e;
25                 } while ((e = e.next) != null);
26             }
27         }
28         return null;
29     }
get

 

 1 public V remove(Object key) {
 2         Node<K,V> e;
 3         return (e = removeNode(hash(key), key, null, false, true)) == null ?
 4             null : e.value;
 5     }
 6 
 7     //matchValue为true时表示只有key和value都对应的情况下才remove
 8     //movable为true时表示在TreeNode中移除该节点同时移动其他节点
 9     //removeNode的条件判断和getNode的几乎相同,在此不再赘述
10     final Node<K,V> removeNode(int hash, Object key, Object value,
11                                boolean matchValue, boolean movable) {
12         Node<K,V>[] tab; Node<K,V> p; int n, index;
13         if ((tab = table) != null && (n = tab.length) > 0 &&
14             (p = tab[index = (n - 1) & hash]) != null) {
15             Node<K,V> node = null, e; K k; V v;//node存储要删除的节点
16             if (p.hash == hash &&
17                 ((k = p.key) == key || (key != null && key.equals(k))))
18                 node = p;
19             else if ((e = p.next) != null) {
20                 if (p instanceof TreeNode)
21                     node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
22                 else {
23                     do {
24                         if (e.hash == hash &&
25                             ((k = e.key) == key ||
26                              (key != null && key.equals(k)))) {
27                             node = e;
28                             break;
29                         }
30                         p = e;//p记录上一个节点
31                     } while ((e = e.next) != null);
32                 }
33             }
34         //找到删除节点并判断matchValue
35             if (node != null && (!matchValue || (v = node.value) == value ||
36                                  (value != null && value.equals(v)))) {
37                 //删除节点为TreeNode
38         if (node instanceof TreeNode)
39                     ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
40                 //删除节点为单一节点
41         else if (node == p)
42                     tab[index] = node.next;
43         //删除节点为链表
44                 else
45                     p.next = node.next;
46                 ++modCount;
47                 --size;
48                 afterNodeRemoval(node);//LinkedHashMap中实现,在此为空
49                 return node;
50             }
51         }
52         return null;
53     }
remove

 

 1 //直接将table内所有元素置为null
 2 //剩下的就交给GC完成
 3 public void clear() {
 4         Node<K,V>[] tab;
 5         modCount++;
 6         if ((tab = table) != null && size > 0) {
 7             size = 0;
 8             for (int i = 0; i < tab.length; ++i)
 9                 tab[i] = null;
10         }
11     }
clear

 

在JDK1.8中还增加了一些关于“默认值“和”指定值”增删改查的功能,在这里不再详细分析,

只给出方法签名和用处,代码本身也比较简单,感兴趣的可以自己去了解一下。

 1 //获取指定key对应的value,获取失败则返回defaultValue
 2 public V getOrDefault(Object key, V defaultValue) 
 3 
 4 //只有key在原表中不存在时才插入
 5 public V putIfAbsent(K key, V value) 
 6 
 7 //移除指定key-value的节点,有一个不对应都不移除
 8 public boolean remove(Object key, Object value) 
 9 
10 //替换指定key中的值,若原值不为oldValue不替换
11 public boolean replace(K key, V oldValue, V newValue) 
jdk1.8

HashMap的遍历

Map和LIst不同,因为Map有两个泛型参数,所以进行遍历的时候一个迭代器不能满足所有迭代需求,

熟悉HashMap的同学应该知道,JDK提供了3个便于我们访问的方法,而其返回的对应是3个集合。

这3个方法是values(),keySet(),EntrySet()。

从命名和Map我们都可以知道,后两个是不能重复的Set,而第一个只是Collection,

更值得一提的是,他们都是HashMap的内部类,在这里只用EntrySet()来举例,其他两个都差不多甚至更简单。

 1 public Set<Map.Entry<K,V>> entrySet() {
 2         Set<Map.Entry<K,V>> es;
 3         return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
 4     }
 5 
 6     final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
 7     //返回set的长度,也就是HashMap的元素多少
 8         public final int size()                 { return size; }
 9     //直接调用HashMap的clear
10         public final void clear()               { HashMap.this.clear(); }
11     //返回这个Set的迭代器
12         public final Iterator<Map.Entry<K,V>> iterator() {
13             return new EntryIterator();
14         }
15     //判断Set是否包含某个Map.Entry,核心是调用getNode
16         public final boolean contains(Object o) {
17             if (!(o instanceof Map.Entry))
18                 return false;
19             Map.Entry<?,?> e = (Map.Entry<?,?>) o;
20             Object key = e.getKey();
21             Node<K,V> candidate = getNode(hash(key), key);
22             return candidate != null && candidate.equals(e);
23         }
24     //删除某个节点,核心是调用removeNode
25         public final boolean remove(Object o) {
26             if (o instanceof Map.Entry) {
27                 Map.Entry<?,?> e = (Map.Entry<?,?>) o;
28                 Object key = e.getKey();
29                 Object value = e.getValue();
30                 return removeNode(hash(key), key, value, true, true) != null;
31             }
32             return false;
33         }
34 
35     //返回并行用的迭代器,在这不详细说明
36         public final Spliterator<Map.Entry<K,V>> spliterator() 
37         public final void forEach(Consumer<? super Map.Entry<K,V>> action) 
38     }
entrySet

可能有的同学会问了,为什么不用Node<K,V>而要用Entry<K,V>呢,搞的还要转换。

因为之前版本都是用的Entry,突然改成Node会使很多代码要重写,不太合适,而且Entry在遍历的时候已经足够使用。

遍历的时候仍是采取了迭代器的方式。

 1 final class EntryIterator extends HashIterator
 2         implements Iterator<Map.Entry<K,V>> {
 3         public final Map.Entry<K,V> next() { return nextNode(); }
 4     }
 5 
 6     abstract class HashIterator {
 7         Node<K,V> next;        // 下一个要返回的Node
 8         Node<K,V> current;     // 现在光标所指Node
 9         int expectedModCount;  // 快速失败用
10         int index;             // 现在光标索引
11 
12         HashIterator() {
13             expectedModCount = modCount;
14             Node<K,V>[] t = table;
15             current = next = null;
16             index = 0;
17             if (t != null && size > 0) {//找到第一个不为空的节点
18                 do {} while (index < t.length && (next = t[index++]) == null);
19             }
20         }
21 
22         public final boolean hasNext() {
23             return next != null;
24         }
25 
26         final Node<K,V> nextNode() {
27             Node<K,V>[] t;
28             Node<K,V> e = next;
29             if (modCount != expectedModCount)
30                 throw new ConcurrentModificationException();
31             if (e == null)
32                 throw new NoSuchElementException();
33         //找到下一个节点
34             if ((next = (current = e).next) == null && (t = table) != null) {
35                 do {} while (index < t.length && (next = t[index++]) == null);
36             }
37             return e;
38         }
39 
40     //移除当前元素,核心是removeNode
41         public final void remove() {
42             Node<K,V> p = current;
43             if (p == null)
44                 throw new IllegalStateException();
45             if (modCount != expectedModCount)
46                 throw new ConcurrentModificationException();
47             current = null;
48             K key = p.key;
49             removeNode(hash(key), key, null, false, false);
50             expectedModCount = modCount;
51         }
52     }
EntryIterator

可以发现,EntrySet的迭代器和HashMap中数据内容是息息相关的,

也就是说是互相影响的,这也是HashMap不能在多线程使用的原因之一。

 

 

 

推荐阅读