/** * This method is invoked whenever the value in an entry is * overwritten by an invocation of put(k,v) for a key k that's already * in the HashMap. */ voidrecordAccess(HashMap<K,V> m){ }
/** * This method is invoked whenever the entry is * removed from the table. */ voidrecordRemoval(HashMap<K,V> m){ } }
/** * The default initial capacity - MUST be a power of two. */ // 默认初始容量-必须为2的幂。 staticfinalint DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */ // 最大容量,如果两个构造函数都使用参数隐式指定了更高的值,则使用该容量。必须是两个<= 1 << 30的幂。 staticfinalint MAXIMUM_CAPACITY = 1 << 30;
/** * The load factor used when none specified in constructor. */ // 在构造函数中未指定时使用的负载系数。 staticfinalfloat DEFAULT_LOAD_FACTOR = 0.75f;
/** * An empty table instance to share when the table is not inflated. */ // 当表未膨胀时要共享的空表实例。 staticfinal Entry<?,?>[] EMPTY_TABLE = {};
/** * The table, resized as necessary. Length MUST Always be a power of two. */ // 该表,根据需要调整大小。长度必须始终为2的幂。 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
/** * The number of key-value mappings contained in this map. */ // // 此映射中包含的键-值映射数。 transientint size;
/** * The next size value at which to resize (capacity * load factor). * @serial */ // 下一个要调整大小的大小值(容量负载因子)。 // If table == EMPTY_TABLE then this is the initial capacity at which the // table will be created when inflated. int threshold;
/** * The load factor for the hash table. * * @serial */ // 哈希表的负载因子。 finalfloat loadFactor;
/** * The number of times this HashMap has been structurally modified * Structural modifications are those that change the number of mappings in * the HashMap or otherwise modify its internal structure (e.g., * rehash). This field is used to make iterators on Collection-views of * the HashMap fail-fast. (See ConcurrentModificationException). */ // 对该HashMap进行结构修改的次数结构修改是指更改HashMap中的映射次数或以其他方式修改其内部结构(例如重新哈希)的修改。此字段用于使HashMap的Collection-view上的迭代器快速失败。 (请参见ConcurrentModificationException)。 transientint modCount;
/** * The default threshold of map capacity above which alternative hashing is * used for String keys. Alternative hashing reduces the incidence of * collisions due to weak hash code calculation for String keys. * <p/> * This value may be overridden by defining the system property * {@code jdk.map.althashing.threshold}. A property value of {@code 1} * forces alternative hashing to be used at all times whereas * {@code -1} value ensures that alternative hashing is never used. */ // 映射容量的默认阈值,高于该阈值时,字符串键将使用替代哈希。备用哈希可减少由于字符串键的哈希码计算能力较弱而导致的冲突发生率。 <p>可以通过定义系统属性{@code jdk.map.althashing.threshold}来覆盖此值。属性值{@code 1}强制始终使用替代哈希,而{@code -1}值确保从不使用替代哈希。 staticfinalint ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
3、构造方法
HashMap()
1 2 3 4 5 6 7 8
/** * Constructs an empty <tt>HashMap</tt> with the default initial capacity * (16) and the default load factor (0.75). */ // 构造一个空的<tt> HashMap <tt>,它具有默认的初始容量(16)和默认的负载系数(0.75)。 publicHashMap(){ this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); }
HashMap(int initialCapacity)
1 2 3 4 5 6 7 8 9 10 11
/** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and the default load factor (0.75). * * @param initialCapacity the initial capacity. * @throws IllegalArgumentException if the initial capacity is negative. */ //构造一个新的<tt> HashMap <tt>,其映射与指定的<tt> Map <tt>相同。 <tt> HashMap <tt>是使用默认负载因子(0.75)和足以将映射保存在指定的<tt> Map <tt>中的初始容量创建的。 @param m要在其 Map 中放置其映射的 Map @throws NullPointerException如果指定的 Map 为null publicHashMap(int initialCapacity){ this(initialCapacity, DEFAULT_LOAD_FACTOR); }
/** * Constructs a new <tt>HashMap</tt> with the same mappings as the * specified <tt>Map</tt>. The <tt>HashMap</tt> is created with * default load factor (0.75) and an initial capacity sufficient to * hold the mappings in the specified <tt>Map</tt>. * * @param m the map whose mappings are to be placed in this map * @throws NullPointerException if the specified map is null */ // 构造一个新的<tt> HashMap <tt>,其映射与指定的<tt> Map <tt>相同。 <tt> HashMap <tt>是使用默认负载因子(0.75)和足以将映射保存在指定的<tt> Map <tt>中的初始容量创建的。 @param m要在其 Map 中放置其映射的 Map @throws NullPointerException如果指定的 Map 为null publicHashMap(Map<? extends K, ? extends V> m){ this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); inflateTable(threshold);
/** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt>. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with <tt>key</tt>.) */ // 将指定值与该映射中的指定键相关联。如果该映射先前包含该键的映射,则将替换旧值。 // @param 与指定值关联的键key // @param 与指定键关联的值 // @return 与<tt> key <tt>或<tt> null <tt>关联的先前值<tt> key <tt>没有映射。 (返回<tt> null <tt>也可以表明该映射先前将<tt> null <tt>与<tt> key <tt>关联。) public V put(K key, V value){ // 第一次调用时,table是空的,进行初始化 if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) // 如果 key 值为空,则调用 putForNullKey 的方法 return putForNullKey(value); // 计算 hash 值 int hash = hash(key); // 计算 key 在 Entry 数组数组中的位置,相当于对该数组取余。 int i = indexFor(hash, table.length); // 遍历该位置上的链表 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // 如果存在相同的 key 值, 则直接覆盖并返回旧值 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } }
/** * Retrieve object hash code and applies a supplemental hash function to the * result hash, which defends against poor quality hash functions. This is * critical because HashMap uses power-of-two length hash tables, that * otherwise encounter collisions for hashCodes that do not differ * in lower bits. Note: Null keys always map to hash 0, thus index 0. */ /** * 检索对象哈希码,并将补充哈希函数应用于 * 结果哈希,以防止质量差的哈希函数。 * 这很关键,因为HashMap使用2的幂的哈希表,否则 * 哈希码的冲突在低位没有区别。注意:空键始终映射到哈希0,因此索引为0。 */ finalinthash(Object k){ int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); }
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). // 此函数可确保在每个位位置仅相差 // 恒定倍数的hashCode具有有限的冲突次数(默认负载因子为约8)。 h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
indexFor(int h, int length)
1 2 3 4 5 6 7 8 9
/** * Returns index for hash code h. */ // 返回哈希码h的索引。 staticintindexFor(int h, int length){ // hashCode 逻辑与(length-1) 所以要长度必须为2的非零幂 // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1); }
addEntry(int hash, K key, V value, int bucketIndex)
/** * Adds a new entry with the specified key, value and hash code to * the specified bucket. It is the responsibility of this * method to resize the table if appropriate. * * Subclass overrides this to alter the behavior of put method. */ /** 将具有指定键,值和哈希码的新条目添加到指定存储桶。如果有必要,此方法负责调整表的大小。 子类重写此方法以更改put方法的行为。 */ // 添加元素, bucketIndex 表示数组下标 voidaddEntry(int hash, K key, V value, int bucketIndex){ // 元素个数大于阈值,并且数组元素不为空 if ((size >= threshold) && (null != table[bucketIndex])) { // 2倍扩容 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; // 扩容后重新计算数组的下标值 bucketIndex = indexFor(hash, table.length); } // 创建元素节点 createEntry(hash, key, value, bucketIndex); }
/** * Rehashes the contents of this map into a new array with a * larger capacity. This method is called automatically when the * number of keys in this map reaches its threshold. * * If current capacity is MAXIMUM_CAPACITY, this method does not * resize the map, but sets threshold to Integer.MAX_VALUE. * This has the effect of preventing future calls. * * @param newCapacity the new capacity, MUST be a power of two; * must be greater than current capacity unless current * capacity is MAXIMUM_CAPACITY (in which case value * is irrelevant). */ / ** * 将此映射的内容重新映射为 * 具有更大容量的新数组。当此映射中的 * 键数达到其阈值时,将自动调用此方法。 * * 如果当前容量为MAXIMUM_CAPACITY,则此方法不会 * 调整地图大小,而是将阈值设置为Integer.MAX_VALUE。 * 这样可以防止将来的通话。 * * @param newCapacity新容量,必须是2的幂; * 必须大于当前容量,除非当前 * 容量为MAXIMUM_CAPACITY(在这种情况下,无关紧要)。 * / // 扩容 voidresize(int newCapacity){ Entry[] oldTable = table; int oldCapacity = oldTable.length; // 数组最大扩容到2的30次方 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; }
/** * Like addEntry except that this version is used when creating entries * as part of Map construction or "pseudo-construction" (cloning, * deserialization). This version needn't worry about resizing the table. * * Subclass overrides this to alter the behavior of HashMap(Map), * clone, and readObject. */ /** * 与addEntry相似,只是在创建条目时将使用此版本 * 作为Map构造或“伪构造”(克隆, * 反序列化)的一部分。此版本无需担心调整表的大小。 * * 子类重写此方法,以更改HashMap(Map), * clone和readObject的行为。 */ // 创建元素,放在头节点 voidcreateEntry(int hash, K key, V value, int bucketIndex){ Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }
/** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key==null ? k==null : * key.equals(k))}, then this method returns {@code v}; otherwise * it returns {@code null}. (There can be at most one such mapping.) * * <p>A return value of {@code null} does not <i>necessarily</i> * indicate that the map contains no mapping for the key; it's also * possible that the map explicitly maps the key to {@code null}. * The {@link #containsKey containsKey} operation may be used to * distinguish these two cases. * * @see #put(Object, Object) */ public V get(Object key){ if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key);
returnnull == entry ? null : entry.getValue(); }
getForNullKey()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/** * Offloaded version of get() to look up null keys. Null keys map * to index 0. This null case is split out into separate methods * for the sake of performance in the two most commonly used * operations (get and put), but incorporated with conditionals in * others. */ private V getForNullKey(){ if (size == 0) { returnnull; } for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } returnnull; }
/** * Returns the entry associated with the specified key in the * HashMap. Returns null if the HashMap contains no mapping * for the key. */ final Entry<K,V> getEntry(Object key){ if (size == 0) { returnnull; }
int hash = (key == null) ? 0 : hash(key); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } returnnull; }
6. 常见面试题
6.1 HashMap的put方法逻辑
判断entry是否是空数组,如果是初始化一个长度是16,阈值是12的数组
判断key是否等于null,如果是null,就放在下标是0的数组位置上,并插入头结点
对key的hashCode二次hash,并对(length-1)逻辑与,算出数组下标位置
遍历该下标位置上的链表,如果找到该key,就覆盖旧值并返回
判断当前元素个数是否大于阈值,如果大于就执行2倍扩容,把原数组的元素重新hash到新数组中
用当前key创建一个节点,插到下标数组链表的头结点
6.2 为什么HashMap的容量必须是2的倍数
1 2 3
static int indexFor(int h, int length) { return h & (length-1); }