目录
JDK7中哈希冲突时使用链表存储冲突元素,当出现大量哈希冲突元素,那么在单链表查找一个元素的复杂度为O(N),为了优化出现大量哈希冲突元素的查找问题,JDK8中规定:当单链表存储元素个数超过阈值TREEIFY_THRESHOLD(8)时,将单链表转换为红黑树,红黑树查找元素复杂度为O(logN),提高了查找效率,JDK8中HashMap的存储结构:
内部字段及构造方法
Node类
使用Node类存储键值对元素。
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; } }TreeNode类
TreeNode是构成红黑树的基本元素。
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; //构造一个树结点 TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } }内部字段
//数组初始容量,为16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //数组最大容量 static final int MAXIMUM_CAPACITY = 1 << 30; //默认加载因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; //单链表转化为红黑树的阈值 static final int TREEIFY_THRESHOLD = 8; /** * 主要用于resize()扩容过程中, 当对原来的红黑树根据hash值拆分成两条链表后, * 如果拆分后的链表长度 <=UNTREEIFY_THRESHOLD, 那么就采用链表形式管理hash值冲突; * 否则, 采用红黑树管理hash值冲突. */ static final int UNTREEIFY_THRESHOLD = 6; /** * 当集合中的容量大于这个值时,表中的桶才能进行树化 ,否则桶内元素太多时会扩容, * 而不是树形化 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD */ static final int MIN_TREEIFY_CAPACITY = 64; //第一次使用是初始化,数组长度总是2的幂次 transient Node<K,V>[] table; transient int size; int threshold; final float loadFactor;构造方法
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; //这里只是初始化,最终赋值在resize方法里 this.threshold = tableSizeFor(initialCapacity); }哈希值与索引计算
hash(Object key)
在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。
这个方法非常巧妙,它通过h & (table.length -1)来得到该对象的保存位,而HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。计算过程如下图:
static final int hash(Object key) { int h; return (key ==
