HashMap继承了AbstractMap<K,V>类,实现了Map<K,V>,Cloneable,Serializable接口,底层由数组+链表和红黑树(jdk8之前没有红黑树)来实现。源码需要关注的主要是构造函数,hash函数,put/get函数,resize函数,remove函数,clear函数等,以及内部字段。
-
基本字段
transient Node<K,V>[] table;//这是HashMap底层的数组,所有的Hash结果都会作为这个数组的index transient Set<Map.Entry<K,V>> entrySet;//用于HashMap遍历的一个set transient int size;//当前HashMap的元素个数 transient int modCount;//HashMap结构变化的次数,用于Fast-Fail机制,值得一提的是,jdk8中的modCount不再是volatile的,因为HashMap本就不是为多线程环境设计的,所以大量的用于单线程环境中,volatile关键字对写的效率影响很大,其次volatile不是线程安全的,就算加上也起不到特别大的作用 int threshold;//HashMap resize的阈值,大于该值会触发HashMap扩容 threshold = capacity * loadFactor,需要注意HashMap内部不存在capacity这个字段,而是通过table.length读取 float loadFactor;//HashMap的装载因子,根据Hash的原理,实际元素数量大于填充度大于装载因子之后可能会出现剧烈的Hash碰撞 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认的HashMap初始容量是16,调用无参构造函数用到 static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量 static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认装载因子 static final int TREEIFY_THRESHOLD = 8;//链表上的节点如果大于等于该值,会触发链表转化为红黑树,优化查询效率从O(n)到 O(logn),当负载因子为 0.75 时,根据泊松分布,同一个Hash出现8个节点的概率仅为0.00000006 static final int UNTREEIFY_THRESHOLD = 6;//链表上节点小于等于该值,会退化为链表 static final int MIN_TREEIFY_CAPACITY = 64;//在转化为红黑树的时候,会进行一次判断,如果当前table数组的长度小于这个值,则会再进行一次resize,而不是直接转化为红黑树
-
构造函数
共四个构造函数
public HashMap(int initialCapacity, float loadFactor);//指定容量和加载因子 public HashMap(int initialCapacity)//仅指定容量 public HashMap() public HashMap(Map<? extends K, ? extends V> m)//使用一个hashmap来构造
主要看一下第一个带参构造函数
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; /** * 需要注意的是,指定容量,并不代表就会创建这么大的table数组, * HashMap通过一个tableSizeFor函数将initialCapacity转化为大于等于他的最小2^n, * 然后赋值给threshold,在resize的时候,会创建threshold这么大的table数组, * 最后通过装载因子重新计算,为threshold赋值 */ this.threshold = tableSizeFor(initialCapacity); }
-
hash函数
static final int hash(Object key) { int h; //对key的HashCode的后16位和前16位进行异或,增强Hash分布的均匀性 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
Hash函数的设计非常重要,需要保证Hash的均匀性,且计算不能太复杂
-
resize函数
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) {//非初始化,因为HashMap已经有容量了 if (oldCap >= MAXIMUM_CAPACITY) {//size已经达到了最大的扩容阈值 threshold = Integer.MAX_VALUE;//进一步调大扩容阈值,而不对size操作了 return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // 这是Hashmap扩容中最核心的代码,双倍扩容 } else if (oldThr > 0) // 带参构造函数的初始化,将HashMap的容量设置为threshold 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值,保证符合threshold = capacity * loadFactor threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; //由于Hashmap的2倍扩容,所以reHash一定会分成两个链表(红黑树) if (oldTab != null) { //扩容的ReHash核心代码,初始化不会进入此分支 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) //链表长度为1,直接rehash newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) //将红黑树拆分为两个,size小于6退化为链表 ((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) { //和原来size与为0串成一个链表 if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { //和原来size与为1的串成另一个链表 if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead;//与为0的,放到原来的位置 } if (hiTail != null) {//与为1的,放到原来的index+size的位置 hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
resize函数在初始化和put的时候都会调用,如果容量已经达到了最大就不在扩容,调整阈值为Int最大值;如果是在正常扩容情况下,会进行二倍扩容;如果是在有参构造函数以后调用,会创建大小等于threshold的table数组,然后更新threshold值;如果是无参构造函数之后调用,则一切使用默认值。扩容完毕之后进行数据的拷贝,也称为reHash,reHash没有重新计算Hash值,而是将hash值和原来的size进行位与操作,reHash会将链表/红黑树拆分为两个,然后分别连到原始的hash的index下,和原始hash的index+原size的index下。
-
put函数
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
put函数是一个空壳函数,核心代码都在putVal函数里
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) //不存在Hash碰撞 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)))) //因为有null判断,所以HashMap支持key为null,存在一个equal的key 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) // -1因为需要插入新的节点 treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) //是否需要覆盖 e.value = value; //LinkedHashMap继承HashMap,对LinkedHashMap起作用,HashMap中为空方法 afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); //LinkedHashMap中用到 afterNodeInsertion(evict); return null; }
put函数是先对HashMap进行空判断,然后分为存在碰撞和不存在碰撞两种情况,存在碰撞又分为红 黑树插入和链表插入,链表插入后需要判断是否需要进行红黑树转化。最后需要进行扩容判断。
-
get函数
public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }
同样get函数也是一个空壳函数,核心代码都在getNode函数中
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) { //首先map需要非空,hash的index存在值 //((k = first.key) == key 这句代码说明了HashMap的key可以为空 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; }
看完put函数,看get函数就很简单了
-
remove函数
public V remove(Object key) { Node<K,V> e; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; }
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在查找符合条件的节点 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; }
-
foreach函数
public void forEach(BiConsumer<? super K, ? 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.key, e.value); //函数式编程,编程者实现消费动作,用lambda表达式遍历 } if (modCount != mc) //Fast-Fail机制 throw new ConcurrentModificationException(); } }
Example:
public static void main(String[] args) { Map<String, String> map = new HashMap<>(); map.put("aaa", "aaa value"); map.put("bbb", "bbb value"); map.forEach((a, b)->{ System.out.println(a + " " + b); }); }