属性分析
/** * 默认初始值,必须是2的次方 */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /** * 最大容量 */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * 默认的负载因子 * 0.75是对空间和时间效率的一个平衡选择,建议大家不要修改 * 除非在时间和空间比较特殊的情况下,如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值; * 相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 一个桶的树化阈值 * 当链表长度超过这个值时,自动转化为红黑树 */ static final int TREEIFY_THRESHOLD = 8; /** * 一个红黑树的还原阈值 * 链表长度小于这个值时,就会把结构还原为链表结构 * 这个值应当比上面那个小,为了避免频繁转换 */ static final int UNTREEIFY_THRESHOLD = 6; /** * 哈希表的最小树形化容量 * 当哈希表的容量大于这个值时,表中的桶才能进行树形化 * 否则桶内元素太多时会扩容,而不是树形化 * 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD */ static final int MIN_TREEIFY_CAPACITY = 64; /** * 存储元素的的数组 * 长度始终为2的次方 */ transient HashMap.Node<K,V>[] table; /** * entrySet()方法返回的结果集 */ transient Set<Map.Entry<K,V>> entrySet; /** * 存放元素的数量 */ transient int size; /** * HashMap内部结构发生变化的次数,主要用于fail-fast机制。 * 强调一点,内部结构发生变化指的是结构发生变化,例如put新键值对,但是某个key对应的value值被覆盖不属于结构变化。 */ transient int modCount; /** * 等于(capacity * load factor) * 当实际大小大于这个值时,将会进行扩容 */ int threshold; /** * 负载因子 */ final float loadFactor;
四个构造函数
/** * 指定初始容量和负载因子 */ public HashMap(int initialCapacity, float loadFactor) { // 如果initialCapacity为负数,则抛出异常 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); // 如果超过最大容量,则为最大容量 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; // 如果loadFactor为负数,或者不是数字,则抛出异常 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; // 调用计算容量的方法,返回大于initialCapacity的最小2的幂。 this.threshold = tableSizeFor(initialCapacity); } /** * 指定初始容量(不能为负数,否则会有异常) * loadFactor为默认值0.75 */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * 默认构造函数, 参数均使用默认值 * initialCapacity = 16,loadFactor = 0.75 */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } /** * 将Map类型的m存入本HashMap */ public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; // 0.75 putMapEntries(m, false); }
存数据
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i; // 如果当前数组table为null或者长度为0,则进行初始化 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 计算下标,如果对应节点的值为null,则新建节点,直接赋值 if ((p = tab[i = (n - 1) & hash]) == null) { tab[i] = newNode(hash, key, value, null); } // 否则,存在冲突 else { HashMap.Node<K,V> e; K k; // 判断key是否已经存在,如果存在直接覆盖 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 判断是不是TreeNode类型,也就是判断是不是红黑树 else if (p instanceof HashMap.TreeNode) // 红黑树处理冲突,执行putTreeVal方法 e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 如果都不是,则为链表 else { // 链表处理冲突 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { // e为空,表示到表尾也没有找到key的相同节点,则新建节点 p.next = newNode(hash, key, value, null); // 判断是否要转化为红黑树 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; // 更新p } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; // map结构被修改次数自增 // 当前大小是否大于阈值,如果大于则扩容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
图片来自美团
扩容
final Node<K,V>[] resize() { // 当前的哈希桶 oldTab Node<K,V>[] oldTab = table; // 当前哈希桶的容量 oldCap int oldCap = (oldTab == null) ? 0 : oldTab.length; // 当前的阈值 oldThr int oldThr = threshold; // 初始化新的容量和阈值为0 int newCap, newThr = 0; // 如果当前容量大于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 } // 如果oldCap<=0也就是当前表为空,但是有阈值。 else if (oldThr > 0) // initial capacity was placed in threshold // 那么新表的容量就等于旧的阈值 newCap = oldThr; // 如果oldCap<=0且oldThr<=0,也就是表为空,也没有阈值 else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; // 给他默认容量16 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 阈值为负载因子0.75f*默认容量16=12 } // 如果新的阈值为0 if (newThr == 0) { // 根据新表容量newCap和负载因子loadFactor计算新阈值 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) { // 取出当前节点e Node<K,V> e; // 如果当前桶中有元素,则将链表赋值给e if ((e = oldTab[j]) != null) { // 将原哈希桶置空以便GC oldTab[j] = null; // 如果当前链表中就一个元素(没有发生哈希碰撞) if (e.next == null) // 直接将这个元素放到新的哈希桶里 newTab[e.hash & (newCap - 1)] = e; // 如果e是树节点,则按照树节点处理 else if (e instanceof HashMap.TreeNode) ((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap); // 如果e是链表节点,则按照链表结构处理 // 因为扩容是容量翻倍,所以原链表上的每个节点,现在可能存放在原来的下标,即low位, 或者扩容后的下标,即high位。 // high位=low位+原哈希桶容量 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; // 这里又是一个利用位运算 代替常规运算的高效点: // 利用哈希值 与 旧的容量,可以得到哈希值去模后,是大于等于oldCap还是小于oldCap, // 等于0代表小于oldCap,应该存放在低位,否则存放在高位 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); // 将低位链表存放在原index处 if (loTail != null) { loTail.next = null; newTab[j] = loHead; } // 将高位链表存放在新index处 if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }