本文共 18562 字,大约阅读时间需要 61 分钟。
/** The default initial capacity - MUST be a power of two. */static final int DEFAULT_INITIAL_CAPACITY = 16; // 数组table的默认初始化大小,容量必须是2^n形式的数/** * 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. */static final int MAXIMUM_CAPACITY = 1 << 30; // hash桶最大数量(table数组的最大长度),size超过此数量之后无法再扩容了/** The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认加载因子
/** The table, resized as necessary. Length MUST Always be a power of two. */transient Entry[] table; // 底层的hash桶数组,长度必须是2^n,容量不足时可以扩容/** The number of key-value mappings contained in this map. */transient int size; // K-V对的数量。注意,为了兼容size方法才使用int,HashMap的实际size可能会大于Integer.MAX_VALUE,理论上long类型才是比较好的值,实际中大多数int型也够用/** The next size value at which to resize (capacity * load factor). */int threshold; // 扩容阈值,一般值为table.length * loadFactor,不能扩容时使用Integer.MAX_VALUE来表示后续永远不会扩容/** The load factor for the hash table. */final float loadFactor; // 加载因子,注意,此值可以大于1/** * 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). */transient volatile int modCount; // 大多数实现类都有的modCountprivate transient Set> entrySet = null;// keySet values继承使用AbstractMap的父类的属性
static class Entryimplements Map.Entry { final K key; V value; Entry next; final int hash; // final的,扩容时hash值还是使用的旧值,只是重新计算索引再散列 Entry(int h, K k, V v, Entry n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } public final int hashCode() { return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } public final String toString() { return getKey() + "=" + getValue(); } // 提供给子类实现的方法,在LinkedHashMap中有实现 void recordAccess(HashMap m) {} void recordRemoval(HashMap m) {}}
// 1.6的构造方法是会真正初始化数组的,到了1.7就开始使用懒初始化,在第一次进行put/putAll等操作时才会真正初始化table数组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); // Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity) // 用循环找出满足的2^n capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; // 真正初始化table数组 init(); // 这个方法里面什么都没做}public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR);}// 默认构造方法,相当于new HashMap(16, 0.75f)public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; // 真正初始化数组 init();}// loadFactor使用默认值0.75f,因为m是接口类型,可能没有loadFactor这个属性public HashMap(Map m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); putAllForCreate(m); // 因为m是一个空的map}void init() {}// 特化的一个put,使用createEntry而不是addEntry,不会触发扩容(容量已经设置好了),也不会修改modCountprivate void putForCreate(K key, V value) { int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); /** * Look for preexisting entry for key. This will never happen forclone or deserialize. * It will only happen for construction if the input Map is a sorted map whose ordering is inconsistent w/ equals. */ // 因为不同的Map实现中判别“相等”的方式可能不一样,因此HashMap这里需要用自己的方式再比较下 for (Entrye = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { e.value = value; return; } } createEntry(hash, key, value, i);}private void putAllForCreate(Map m) { for (Iterator > i = m.entrySet().iterator(); i.hasNext();) { Map.Entry e = i.next(); putForCreate(e.getKey(), e.getValue()); }}// 在初始化时使用的一个特化的添加节点的方法void createEntry(int hash, K key, V value, int bucketIndex) { Entry e = table[bucketIndex]; table[bucketIndex] = new Entry (hash, key, value, e); size++;}
/** Returns index for hash code h. */// hash桶定位方法,利用length = 2^n的特性,使用位运算加快速度static int indexFor(int h, int length) { return h & (length-1);}
/** * Applies a supplemental hash function to a given hashCode, 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自己的hash函数,是一个扰动函数,主要是为了避免hashCode方法设计的不够好导致hash冲突过多// indexFor方法只能利用h的最低的n位的信息,因此使用移位来让低位能够附带一些高位的信息,充分利用hashCode的所有位的信息static int hash(int h) { // 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). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4);}
// table数组扩容void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { // 数组达到最大长度时,不能再扩容了 threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable); // 把旧数组上所有节点,重新移动到新数组上正确的地方 table = newTable; threshold = (int)(newCapacity * loadFactor); // 重设阈值,注意这里有点问题。loadFactor可以大于1,newCapacity*loadFactor是个浮点数, // 它可能大于Integer.MAX_VALUE,此时强转后变为Integer.MAX_VALUE,造成后续再也无法扩容。1.7开始修复了这一点}// 基本思路是把旧数组的所有节点全都重新“添加”到新数组对应的hash桶中// 1.6的实现很简单、直接、直观,后续版本有改良的实现void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entrye = src[j]; if (e != null) { src[j] = null; // 这里是把原来的Entry链从头到尾再“put”到新数组里面 // jdk1.6的put是把新节点添加到Entry链的最前面,因此transfer执行后,还在同一条Entry链(只有两条可选,可以看下jdk1.8的注释,后面我也会说)上的节点的相对顺序会颠倒 // 举个例子(数字为hash值,非真实值),扩容transfer前,table[0] = 16 -> 32 -> 48 -> 64 -> 80 -> 96, // 扩容新数组中变成两条了,一条是table[0] = 80 -> 48 -> 16,另一条是table[16] = 96 -> 64 -> 32 // 16, 48, 80(32, 64, 96)还在同一条上,但是它们的相对顺序颠倒了,HashMap的整体的迭代顺序当然也变了,当然本身它ye不保证迭代顺序 do { Entry next = e.next; int i = indexFor(e.hash, newCapacity); // 没有重新计hash值,只是重新计算索引 e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } }}
public V get(Object key) { if (key == null) // key == null 的情况 return getForNullKey(); int hash = hash(key.hashCode()); for (Entrye = table[indexFor(hash, table.length)]; e != null; e = e.next) { // indexFor定位hash桶 Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) // 遍历链表查找 return e.value; } return null;}// 处理 key == null 的情况// 根据putForNullKey方法(后面说)可以知道,key == null的节点,一定放在index = 0的hash桶中,判断null要使用 "=="private V getForNullKey() { for (Entry e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null;}
// jdk1.8,请使用1.8运行,1.8的hash函数比较简单,容易构造数据// 需要用调试器才看得出来在同一条Entry链上,请使用调试器public class TestHashCode { public static void main(String[] args) { Key k = new Key(); Map虽然HashXXX中hashCode优先,但是平时还是不要用这一点,非常迷惑人。而在其他的大多数情况下,==和equals是优先于hashCode的,判断对象相等基本上都是直接使用 ==或者equals,根本不使用hashCode。 所以大家还是要尽量遵守equals和hashCode的通常规定,不要写出奇怪的equals和hashCode方法,同时尽量避免修改已经放到HashXXX中的对象中会改变hashCode和equals结果的field。大多数情况,使用不变类,比如String、Integer等,充当key是一个很好的选择。map = new HashMap<>(); map.put(k, "1"); k.i = 2; // 修改hashCode map.put(k, "2"); // 现在put了两个key "equals 且 ==" 的K-V对,hashCode不一样,实际hash值 k.i = 16; // 修改hashCode map.put(k, "16"); // 现在put了三个key "equals 且 ==" 的K-V对,并且第三个跟第一个在同一条Entry链(index = 0)上,hashCode不一样,实际hash值也不一样 System.err.println(map); // 现在这个HashMap有三个K-V对,它们的key都是 "equals 且 ==" 的 ,但是它们的hashCode各不同,算出来的hash值不一样,在HashMap中这"三个"key是不"相等"的 Key newK = new Key(); newK.i = 16; map.put(newK, "new16"); System.err.println(map); // 又添加了一个,并且也在index = 0的Entry链上,它的hash值和第三个相等,但是equals判断不相等,所以在HashMap看来它跟第三个是不"相等"的 // 因为Key的toString是直接使用Object.toString(),会用到hashCode,因此打印出来的结果中,四个K-V的key看上去都是一样的 } static class Key { int i = 0; public int hashCode() { return i; } }}
public V put(K key, V value) { if (key == null) // 处理 key == null 的情况 return putForNullKey(value); int hash = hash(key.hashCode()); // indexFor定位hash桶 int i = indexFor(hash, table.length); // 先确认是否添加了“相等”的key for (Entrye = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { // “相等”是指满足此条件,上面的hash方法中说了 V oldValue = e.value; e.value = value; e.recordAccess(this); // 此方法HashMap中是空方法,留给子类实现 return oldValue; } } modCount++; addEntry(hash, key, value, i); // 执行真正的添加操作 return null; // 新添加的key,没有旧的value,返回null}// 处理 key == null 的情况,总是把它放在index = 0的hash桶中private V putForNullKey(V value) { // 先确认是否已经添加了null key for (Entry e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(0, null, value, 0); // 执行真正的添加操作 return null;}// 在Entry链的头部插入新的节点,并检查是否需要扩容void addEntry(int hash, K key, V value, int bucketIndex) { Entry e = table[bucketIndex]; table[bucketIndex] = new Entry (hash, key, value, e); // 先把新的节点添加进去 if (size++ >= threshold) // 然后判断是否要扩容,在把size加1 resize(2 * table.length); // 把第(threshold + 1)个添加了再扩容为2倍大小(例如,默认构造的HashMap时,在执行put第13个key互不“相等”的K-V时扩容)}
public V remove(Object key) { Entrye = removeEntryForKey(key); return (e == null ? null : e.value);}// 就是链表中节点的删除,很简单final Entry removeEntryForKey(Object key) { int hash = (key == null) ? 0 : hash(key.hashCode()); // 计算hash值 int i = indexFor(hash, table.length); // 定位hash桶 Entry prev = table[i]; Entry e = prev; while (e != null) { // 遍历链表寻找key“相等”的节点 Entry next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; // 修改指针,删除节点 if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); // 这个方法交给子类实现 return e; } prev = e; e = next; } return e;}
public int size() { return size;}public boolean isEmpty() { return size == 0;}public boolean containsKey(Object key) { return getEntry(key) != null;}public void clear() { modCount++; Entry[] tab = table; for (int i = 0; i < tab.length; i++) tab[i] = null; size = 0;}// 分null、非null两种情况判断,也很好理解public boolean containsValue(Object value) { if (value == null) return containsNullValue(); Entry[] tab = table; for (int i = 0; i < tab.length ; i++) for (Entry e = tab[i] ; e != null ; e = e.next) if (value.equals(e.value)) return true; return false;}// 处理null value的情况private boolean containsNullValue() { Entry[] tab = table; for (int i = 0; i < tab.length ; i++) for (Entry e = tab[i] ; e != null ; e = e.next) if (e.value == null) return true; return false;}public void putAll(Map m) { int numKeysToBeAdded = m.size(); if (numKeysToBeAdded == 0) return; // 这里使用保守的策略,一点小小的优化完善 // 直观的策略(m.size() + size) >= threshold不一定准确,因为两个map中可能会存在许多K-V重叠,可能会白白地扩容一次 // numKeysToBeAdded <= threshold 时本身也只扩容一次,就把这次可能的扩容交给put去进行准确的判断 if (numKeysToBeAdded > threshold) { int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); // 加1是为了有预留空间,避免下一次put就立即扩容 if (targetCapacity > MAXIMUM_CAPACITY) targetCapacity = MAXIMUM_CAPACITY; int newCapacity = table.length; while (newCapacity < targetCapacity) newCapacity <<= 1; if (newCapacity > table.length) resize(newCapacity); } for (Iterator > i = m.entrySet().iterator(); i.hasNext(); ) { Map.Entry e = i.next(); put(e.getKey(), e.getValue()); }}