Map集合类(一.hashMap源码解析jdk1.8)
jdk 8 之前,其内部是由数组+链表来实现的,而 jdk 8 对于链表长度超过 8 的链表将转储为红黑树
1.属性
//节点数组,第一次使用时初始化,后面根据需要调整, transient Node<K,V>[] table; //实际存储的键值对个数 transient int size; //用于迭代防止结构性破坏的标量 transient int modCount; //临界值,等于数组容量*负载因子,添加元素时数组中的元素到达临界值,数组扩容后再添加元素 int threshold; //HashMap 中默认负载因子为 0.75 final float loadFactor; //数组中存放的节点 static class Node<K,V> implements Map.Entry<K,V> { final int hash;//hash值 final K key;//键 V value;//值 Node<K,V> next;//指向的下一个节点 .... } //链表超过数量时红黑树节点 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // red-black tree links TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; ... }
2.构造函数
初始化负载因子及容量大小(放置在临界值变量中)
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); } //通过该方法,我们将获得一个 2 的整数次幂的容量的值,此处存放至 threshold,为后面put方法中初始数组时使用 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; }
该方法例如 传入自定义初始容量大小为19
例如自定义初始化容量为19;最后结果为32
0000 0000 0001 0001 减一
0000 0000 0000 1000 >>>1
0000 0000 0001 1001 或结果25
0000 0000 0000 0110 >>>2
0000 0000 0001 1111 或结果31
0000 0000 0000 0001 >>>4
0000 0000 0001 1111 或结果31
0000 0000 0000 0000 >>>8
0000 0000 0001 1111 或结果31
0000 0000 0000 0000 >>>16
0000 0000 0001 1111 或结果31
3.put元素
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为空则是第一次初始化 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //根据键的 hash 值找到对应的索引位置,如果该位置为 null,说明还没有头结点,于是 newNode 并存储在该位置上。 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { //若该索引位置有值 Node<K,V> e; K k; //若该位置的第一个节点hash值及Key相同,则修改该第一个节点的值 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); //TREEIFY_THRESHOLD=8;若该位置节点数大于等于8,将链表裂变成红黑树 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //在遍历时发现该节点的hash值及key相同,则跳出遍历,在下面将值修改 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //修改,将节点e的值替换为新值 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; //需要扩容,调用resize(); if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
初始化数组或数组扩容
final Node<K,V>[] resize() { ... int oldCap = (oldTab == null) ? 0 : oldTab.length;//旧数组长度 ... //若旧数组长度大于0则已经初始化过 if (oldCap > 0) { //若旧数组长度大于了最大长度限制 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } //若旧数组长度翻倍赋值给新数组长度小于最大长度限制且旧数组长度大于默认长度(16)则临界值翻倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } //若旧临界值大于0,则新数组长度为旧临界值(及构造方法中的tableSizeFor()) else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; //构造函数无参,初始化数组长度为16,临界值为0.75*16 else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } threshold = newThr; 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 { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; //根据hash与运算旧数组长度,分为两个链表 //关于原链表分成2个不同的链表后如何查找节点 //查询节点在putVal和getNode中为:tab[i = (n - 1) & hash],n为数组长度 //假如n为16,二进制为1 0000 hash值为0101 则查找数组中的下标为3的节点 //现扩容为32,二进制为10 0000 hash依然为0101 则扩容后依然查询新数组中下标为3的节点,链表也即为下面lo //假如n为16,hash值为1 1101 则查找数组中下标为13的节点 //现扩容为32,hash值为1 1101 则需查询扩容后的数组下标为(原位置+原数组长度)29的节点,链表即为下面的hi if ((e.hash & oldCap) == 0) { //尾部节点为空 为第一个将原节点e赋值给头节点 if (loTail == null) loHead = e; //此时头节点已存在,将原节点e赋值给新链表尾部节点的下一个 else loTail.next = e; //尾部节点赋值为源节点e loTail = e; } //同上 else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); //lo尾部节点不为空,新数组(原位置)存放该链表的头节点 if (loTail != null) { loTail.next = null; newTab[j] = loHead; } //hi尾部节点不为空,新数组(原位置+原数组长度)存放该链表的头节点 if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } //返回初始化或扩容后的数组 return newTab; }
裂变为红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; //原数组为空或数组长度小于MIN_TREEIFY_CAPACITY=64则选择数组扩容,不转为红黑树 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 { 原单向链表转化为双向链表,node转换为treeNode 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); } }