JDK1.7版本HashMap的源码分析
注意:HashMap的源码本文仅分析JDK1.7,1.8版本的请参考: JDK1.8版本HashMap
JDK1.7版本HashMap
1 结构图
HashMap由数组(本文后续以table表示)+ 链表构成。其本意是用table来存储一个键值对(本文后续以Entry表示),然后可以通过key取hash值同时对数组长度取模,散列分布到数组每个下标下的时候,那取值时间复杂度将为O(1),在最理想的情况下,就是数组一个下标上只存一个Entry值。
但是天不如人愿,并不存在一个如此优秀的Hash函数,让每个Entry的key一定能散列分布在数组每个下标下,总是会出现如下情况:不同的key值,取hash值并取模后,数组下标值一样,那这个时候就出现了Hash冲突。解决Hash冲突方法有挺多种的,而HashMap采用链表来解决,意思是,假如出现Hash冲突后,则在该下标下,形成一个单链表,存储不同的Entry,那么在查询操作的时候,就需要对链表进行遍历,同时调用HashMap的key的equals方法,判断其是否相等,相等则返回该Entry,这也就是HashMap为什么要求key对象一定要实现hashCode方法和equals方法的原因。
2 继承关系
我们来看下HashMap的继承关系图:
左边实现的Cloneable接口和Serializable接口是为了实现克隆和序列化功能,非本文重点关注对象,请忽略。HashMap主要继承父类AbstractMap,而父类实现了Map接口。
2.1 Map接口
下面是Map接口提供的方法:
可以看到Map还有一个内部接口Entry,Entry就是代表键值对,一个key和一个vale值。
2.2 AbstractMap抽象类(非重点父类,请稍微浏览即可)
AbstractMap对于HashMap的作用,仅仅是提供了keySet 和values 变量。而其他的Map方法都在HashMap中重写了一遍。因此请看淡AbstractMap抽象父类。
AbstractMap.java(省略了其实现的方法):
public V put(K key, V value) { throw new UnsupportedOperationException(); } ... public abstract Set<Entry<K,V>> entrySet();
以上两个方法是AbstractMap未实现Map接口的方法。其它方法,AbstractMap基本都实现了。
2.3 HashMap类
HashMap虽然继承了AbstractMap类,但实际上,它基本上重写了AbstractMap类实现Map接口的方法,换句话说,他表面上继承AbstractMap,但暗地里把它实现的方法都重写了一遍,丝毫不留情面。其只使用了父类的两个成员变量keySet和values
2.3.1 字段
/** * 默认初始化容量 */ 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; /** * 空table */ static final Entry<?, ?>[] EMPTY_TABLE = {}; /** * table,用来存放键值对,键值对可以单个值,也可以是一个链表 */ transient Entry<K, V>[] table = (Entry<K, V>[]) EMPTY_TABLE; /** * 大小 */ transient int size; /** * 阈值,扩容的主要判断条件之一 */ int threshold; /** * 负载因子 */ final float loadFactor; /** * 结构更改次数的统计,增、删都会加1。也是快速失败的触发依据,后面HashIterator会介绍如何使用该值 */ transient int modCount; /** * 默认备选hash阈值 */ static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE; /** * 注意、注意、注意: * 这两个成员变量来自父类AbstractMap,由于其为default类型,在不同包将访问不到,因此在此声明,以此代替 * 由于keySet、value在父类中的keySet()、values()、clone()方法中用到,而HashMap这几个方法全部重写了,因此不会影响使用 */ transient volatile Set<K> keySet = null; transient volatile Collection<V> values = null;
2.3.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); // 初始化负载因子和阈值 // 注意:阈值在这里并非稳定值,在put方法进行初始化后,会等于数组长度*负载因子的值 this.loadFactor = loadFactor; threshold = initialCapacity; // init是空实现,忽略 init(); }
接收容量的构造器:
// 接收容量的构造器,并将负载因子设置为默认的0.75 public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
最常用的构造器,无参构造:
// 用得最多的构造器,默认容量16、负载因子0.75 public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); }
接收一个map的构造器:
public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); inflateTable(threshold); putAllForCreate(m); }
2.3.3 get(Object key)方法的实现原理
// 取值 public V get(Object key) { // 由于HashMap支持一个null值,因为key为null的时候,作特殊处理 // null的值固定在table[0]下 if (key == null) return getForNullKey(); // 代码1 // 取entry Entry<K, V> entry = getEntry(key); // 代码2 return null == entry ? null : entry.getValue(); }
由于HashMap支持key=null,而对于key=null的情况,由代码1进行特殊处理,下面是其对应逻辑:
// 取key=null的值 private V getForNullKey() { if (size == 0) { return null; } for (Entry<K, V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; }
遍历table[0]下标下的值,由于key=null的特殊值,固定会放在table[0]下。假如table[0]下的结点,有key为null的,则将该值返回。
当key不为null时,由代码2取key对应的Entry,下面看看getEntry的实现逻辑:
// 获取键值对 final Entry<K, V> getEntry(Object key) { if (size == 0) { return null; } // 计算key的hash值 int hash = (key == null) ? 0 : hash(key); // 遍历某个下标下的链表(如果是链表的话) for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; // 如果key的hash相同 && (key相同或者key的equals方法的值相同)则返回 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; }
该方法分两步,首先根据key,计算该key对应的下标值。
第二步,遍历下标对应的链表,然后分别判断其hash值是否相等且(key相同或者key的equals方法相等)则将该值返回。
// 添加值 public V put(K key, V value) { // 如果table为空,则根据阈值初始化table的值。 // 由于HashMap是延迟初始化的,构造器只给table赋值为EMPTY_TABLE if (table == EMPTY_TABLE) { inflateTable(threshold); // 代码3 } // 特殊处理key=null的情况 if (key == null) return putForNullKey(value); // 代码4 // 计算hash值 int hash = hash(key); // 代码5 // 根据hash值与table长度,取模(实际为与操作)得到下标值 int i = indexFor(hash, table.length); // 代码6 // 判断在当前下标下的链表有没有相同key值的Entry,有则覆盖,并将旧值返回 for (Entry<K, V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 假如没有相同key值,则将为HashMap增加一个新值 // 由于结构发生改变,因此modCount需要+1 modCount++; // 添加新的Entry值 addEntry(hash, key, value, i); // 代码7 return null; }
从上面构造器可以发现,HashMap是延迟初始化的,最开始的时候,给table赋值了一个EMPTY_TABLE,但真正将值设置进来的时候,才去申请capacity对应的空间。
代码3,当table==EMPTY_TABLE的时候,将执行初始化操作,下面代码看看其如何初始化的:
// 初始化table的值 private void inflateTable(int toSize) { // 将容量控制为2的n次方 int capacity = roundUpToPowerOf2(toSize); // 阈值载不大于MAXIMUM_CAPACITY+1的情况下,值等于capacity*loadFactor threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); // 根据容量,创建Entry数组 table = new Entry[capacity]; // 初始化hash种子,可能会对hashSeed字段赋值,hashSeed可能会影响hash(Object key)方法的计算,下面会讲述 initHashSeedAsNeeded(capacity); }
回到put方法的代码,在完成table值的初始化后,往下执行到代码4,会对key==null的情况进行特殊处理:
// 特殊处理key=null的值,将其放在table[0]下 private V putForNullKey(V value) { for (Entry<K, V> 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; }
如果key!=null的时候,计算key对象的hash值,并取模得到key对应table的下标值,下面统一对于代码代码5和代码6进行分析:
代码5:
// 对key值作hash运算 final int hash(Object k) { // hashSeed在大部分情况下是0,当容量超过2147483647后,可能会变为一个随机hashSeed(该情况不做分析) int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } // h为0的时候,下面异或操作结果将是 右边操作数的值,则下面相当于h = k.hashCode() h ^= k.hashCode(); // 以下代码使用了扰动计算,异或了高位+低位的值,避免高位没有参与计算,导致在与table-1做与运算的时候,无法散列在各个下标值下 h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
在hashSeed不为0的时候且key为String类型的时候,可能会调用Hashing.stringHash32进行特殊处理,来的到hash值。
以上情况不满足的时候,将key的hashCode赋值给h,然后通过异或h的多个比特位进行计算。这里用了扰动计算,将hashCode的每个值都用到计算中,以支持散列分布。
代码6:
// 获取当前key下落在table哪个下标,用与运算,等效取模,但性能比取模快 static int indexFor(int h, int length) { // 首先,length要求为2的n次方,2的n次方-1的时候,得到一个二进制是全1的值 // 当h的值去与操作一个全1的值,等效于对 %length // 那为什么不用取模操作,而要与操作呢?因为与操作效率比取模操作速度快很多 return h & (length - 1); }
回到put方法,在计算取得下标值后,将判断当前添加的键值对key是否已存在,开始遍历保存在该下标下的链表,当结点的hash值相等且(key相同或者key的equals方法相等)则覆盖该值,并将旧值返回。
如果以上情况都不满足,则说明当前添加的Entry既不是key==null,又不是key存在的情况。那么开始新增一个键值对,然后返回null。该操作将可能触发扩容。
代码7:
// 添加新的键值对 void addEntry(int hash, K key, V value, int bucketIndex) { // 判断当前容器大小是否大于等于阈值 && 当前table下标的值存在值,将进行扩容操作 if ((size >= threshold) && (null != table[bucketIndex])) { // 扩容为当前table长度的两倍 resize(2 * table.length); // 代码8 // 重新计算扩容后,当前key将位于table的哪个下标 hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } // 创建键值对 createEntry(hash, key, value, bucketIndex); }
代码8的扩容方法,在下面讲述,将解析其为什么在多线程环境下会形成环路。
2.3.5 resize()方法原理及多线程环境下环路的形成原因// 扩容方法,多线程不安全,其他方法也是多线程不安全,但是resize问题比较严重 // 可能形成环形链表,造成死循环,CPU100% void resize(int newCapacity) { // 暂存旧的table Entry[] oldTable = table; // 暂存旧table长度 int oldCapacity = oldTable.length; // 假如旧的table长度等于最大容量,将不做扩容操作 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 根据新容量,新建新table Entry[] newTable = new Entry[newCapacity]; // 执行table上面值得转移,将oldTable上面的值转移到newTable,会重新hash并取模来判断新的下标值 // 多线程环境下,下面方法可能会产生环形链表,造成死循环 transfer(newTable, initHashSeedAsNeeded(newCapacity)); // 将newTable 替换为table table = newTable; // 重新计算阈值 ,控制阈值不能超过MAXIMUM_CAPACITY+1 threshold = (int) Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }
在进行newCapacity(新容量)的合法值校验完成后,创建一个新的Entry数组,然后开始调用transfer进行元素的转移,意思就是把旧数组上面的元素值,进行重新hash并取模,确定其在新数组的下标,我们来看看其如何实现:
// 转移值 void transfer(Entry[] newTable, boolean rehash) { // 获取新的容量 int newCapacity = newTable.length; // 遍历table的Entry for (Entry<K, V> e : table) { // 遍历链表(如果是链表的话) while (null != e) { // 假如线程A取到next,然后时间片用完,CPU切换到线程B继续执行链表值转移操作,并执行完毕 // 那么线程A有了CPU时间片后,继续执行,此时再触发转移值操作,很容易会形成环形链表,造成死循环 Entry<K, V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } // 根据hash值取模,得到在新table的下标值 int i = indexFor(e.hash, newCapacity); // 采用头插法,将当前节点下一个节点指向newTable当前下标的值 e.next = newTable[i]; // 将当前节点作为newTable的头结点 newTable[i] = e; // 将下一个节点赋值给下一轮循环的结点,继续往下执行循环 e = next; } } }
这里注意一点是,newTable是作为方法参数传递进来,每个线程都会创建一个新的newTable,其值是线程私有,而线程不安全的变量,其实是table里面的Entry。具体如何成环路的场景分析,请参考下面的示例图。
图片地址: https://www.processon.com/diagraming/5e06f27ce4b0125e2924abba
2.3.5 keySet()方法的实现原理public Set<K> keySet() { Set<K> ks = keySet; return (ks != null ? ks : (keySet = new KeySet())); }
上述代码为keySet实现原理,很简单,就是在keySet为null的时候,new了一个KeySet对象,那下面我们看看KeySet对象:
private final class KeySet extends AbstractSet<K> { // 实现Set的迭代方法 public Iterator<K> iterator() { return newKeyIterator(); } // 大小值跟HashMap大小一致 public int size() { return size; } // 调用HashMap的containsKey方法 public boolean contains(Object o) { return containsKey(o); } // 调用HashMap的removeEntryForKey方法 public boolean remove(Object o) { return HashMap.this.removeEntryForKey(o) != null; } // 调用HashMap的clear方法 public void clear() { HashMap.this.clear(); } }
由上述代码看出,其具体实现,全都依赖于HashMap。这里可能有个困惑的点,就是我们看到一个Set对象,就会想当然觉得里面的元素都是存储在Set对象里面,其实不然。KeySet对象的值,如元素的值,集合大小等信息,其实都是用着HashMap的成员域。如果你获取到KeySet对象后,调用了remove方法,实际上会改变该key对应在HashMap中的Entry。
我们分析一下iterator方法:
public Iterator<K> iterator() { // 调用迭代器方法时,会创建一个新的Key迭代器 return newKeyIterator(); }
调用HashMap的方法newKeyIterator创建一个KeyIterator对象。
Iterator<K> newKeyIterator() { // 每次调用都会创建一个新的KeyIterator return new KeyIterator(); }
KeyIterator 类:
private final class KeyIterator extends HashIterator<K> { public K next() { return nextEntry().getKey(); } }
其next方法是调用父类HashIterator的nextEntry方法获取一个Entry,然后再获取Entry的key,这跟ValueIterator的实现是对应的。
下面我们看看nextEntry方法的具体实现:
private abstract class HashIterator<E> implements Iterator<E> { Entry<K,V> next; // 需要返回的下一个Entry int expectedModCount; // 快速失败的判断依据,如果该值不等于HashMap的modCount,将抛出ConcurrentModificationException int index; // 当前迭代下标 Entry<K,V> current; // 当前Entry HashIterator() { // 将HashMap结构修改次数保存一个副本,下面迭代的时候,如果两个值不相等,将抛出并发修改异常(其实不太准确,下面案例分析)ConcurrentModificationException expectedModCount = modCount; if (size > 0) { // advance to first entry Entry[] t = table; // 遍历table,直到定位到table不为null的值 while (index < t.length && (next = t[index++]) == null); } } public final boolean hasNext() { return next != null; } final Entry<K,V> nextEntry() { // 判断在执行迭代器期间,HashMap有没有对结构进行修改 if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K,V> e = next; if (e == null) throw new NoSuchElementException(); if ((next = e.next) == null) { Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e; } public void remove() { if (current == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } }
从HashIterator的代码我们可以看出,在其成员域中维护了
3 源码
下面是源码,篇幅较长,上文已将重要方法都讲解了,如果没有兴趣,可以不看。篇幅长,所以可能会比较卡!
package hashmap1_7; import java.io.IOException; import java.io.InvalidObjectException; import java.io.Serializable; import java.util.*; /** * JDK1.7版本,加入个人一些理解、注释 * 当使用该类的时候,记得要把项目的JDK版本切换为1.7,否则会报错 * copy form jdk1.7 * @author wzj * 2019/12/28 */ public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable { /** * 默认初始化容量 */ 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; /** * 空table */ static final Entry<?, ?>[] EMPTY_TABLE = {}; /** * table,用来存放键值对,键值对可以单个值,也可以是一个链表 */ transient Entry<K, V>[] table = (Entry<K, V>[]) EMPTY_TABLE; /** * 大小 */ transient int size; /** * 阈值,扩容的主要判断条件之一 */ int threshold; /** * 负载因子 */ final float loadFactor; /** * 结构更改次数的统计,增、删都会加1。也是快速失败的触发依据,后面HashIterator会介绍如何使用该值 */ transient int modCount; /** * 默认备选hash阈值 */ static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE; /** * 注意、注意、注意: * 这两个成员变量来自父类AbstractMap,由于其为default类型,在不同包将访问不到,因此在此声明,以此代替 * 由于keySet、value在父类中的keySet()、values()、clone()方法中用到,而HashMap这几个方法全部重写了,因此不会影响使用 */ transient volatile Set<K> keySet = null; transient volatile Collection<V> values = null; private static class Holder { /** * 备选hash阈值 */ static final int ALTERNATIVE_HASHING_THRESHOLD; static { // 从系统获取阈值的配置,一般没有配置 String altThreshold = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction( "jdk.map.althashing.threshold")); int threshold; try { // 给阈值赋值 threshold = (null != altThreshold) ? Integer.parseInt(altThreshold) : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT; // 对阈值合法性进行校验 if (threshold == -1) { threshold = Integer.MAX_VALUE; } if (threshold < 0) { throw new IllegalArgumentException("value must be positive integer."); } } catch (IllegalArgumentException failed) { throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed); } // 最终给备选阈值设置值 ALTERNATIVE_HASHING_THRESHOLD = threshold; } } /** * hash种子 */ transient int hashSeed = 0; 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); // 初始化负载因子和阈值 // 注意:阈值在这里并非稳定值,在put方法进行初始化后,会等于数组长度*负载因子的值 this.loadFactor = loadFactor; threshold = initialCapacity; // init是空实现,忽略 init(); } // 接收容量的构造器,并将负载因子设置为默认的0.75 public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } // 用得最多的构造器,默认容量16、负载因子0.75 public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } // 接收一个map的构造器 public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); inflateTable(threshold); putAllForCreate(m); } // 根据传入的值,得到一个接近的2的n次方数 private static int roundUpToPowerOf2(int number) { // 以下操作分三步 // 1、判断是否超过容量最大值,超过则取容量最大值 // 2、判断是否小于最小值,小于则取最小值1 // 3、当上述情况不符合,则说明值在正常范围 // 让该值-1再乘以2,然后调用Integer的highestOneBit方法,向下取一个最近的2的n次方值 // 如:1取1(2^0)、 2-3取2(2^1)、 4-7取4(2^2) 、8-15取8(2^3) 、16-31取16(2^4) return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; } // 初始化table的值 private void inflateTable(int toSize) { // 将容量控制为2的n次方 int capacity = roundUpToPowerOf2(toSize); // 阈值载不大于MAXIMUM_CAPACITY+1的情况下,值等于capacity*loadFactor threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); // 根据容量,创建Entry数组 table = new Entry[capacity]; // 初始化hash种子,可能会对hashSeed字段赋值,hashSeed可能会影响hash(Object key)方法的计算,下面会讲述 initHashSeedAsNeeded(capacity); } void init() { } // 初始化哈希掩码值。我们将初始化推迟到真正需要的时候。 final boolean initHashSeedAsNeeded(int capacity) { // 第一次进入该方法,currentAltHashing一直为false boolean currentAltHashing = hashSeed != 0; // 只有等到容量值超过2147483647,则会触发给hashSeed赋值 // sun.misc.VM.isBooted为虚拟机启动后一直为true && 容量大于最大值 boolean useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); boolean switching = currentAltHashing ^ useAltHashing; if (switching) { // 假如useAltHashing为true,则将hashSeed设置为随机产生的HashSeed hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) : 0; } return switching; } // 对key值作hash运算 final int hash(Object k) { // hashSeed在大部分情况下是0,当容量超过2147483647后,可能会变为一个随机hashSeed(该情况不做分析) int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } // h为0的时候,下面异或操作结果将是 右边操作数的值,则下面相当于h = k.hashCode() h ^= k.hashCode(); // 以下代码使用了扰动计算,异或了高位+低位的值,避免高位没有参与计算,导致在与table-1做与运算的时候,无法散列在各个下标值下 h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } // 获取当前key下落在table哪个下标,用与运算,等效取模,但性能比取模快 static int indexFor(int h, int length) { // 首先,length要求为2的n次方,2的n次方-1的时候,得到一个二进制是全1的值 // 当h的值去与操作一个全1的值,等效于对 %length // 那为什么不用取模操作,而要与操作呢?因为与操作效率比取模操作速度快很多 return h & (length - 1); } public int size() { return size; } public boolean isEmpty() { return size == 0; } // 取值 public V get(Object key) { // 由于HashMap支持一个null值,因为key为null的时候,作特殊处理 // null的值固定在table[0]下 if (key == null) return getForNullKey(); // 取entry Entry<K, V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } // 取key=null的值 private V getForNullKey() { if (size == 0) { return null; } for (Entry<K, V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; } public boolean containsKey(Object key) { return getEntry(key) != null; } // 获取键值对 final Entry<K, V> getEntry(Object key) { if (size == 0) { return null; } // 计算key的hash值 int hash = (key == null) ? 0 : hash(key); // 遍历某个下标下的链表(如果是链表的话) for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; // 如果key相同且值相同,则返回 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; } // 添加值 public V put(K key, V value) { // 如果table为空,则根据阈值初始化table的值。 // 由于HashMap是延迟初始化的,构造器只给table赋值为EMPTY_TABLE if (table == EMPTY_TABLE) { inflateTable(threshold); } // 特殊处理key=null的情况 if (key == null) return putForNullKey(value); // 计算hash值 int hash = hash(key); // 根据hash值与table长度,取模(实际为与操作)得到下标值 int i = indexFor(hash, table.length); // 判断在当前下标下的链表有没有相同key值的Entry,有则覆盖,并将旧值返回 for (Entry<K, V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 假如没有相同key值,则将为HashMap增加一个新值 // 由于结构发生改变,因此modCount需要+1 modCount++; // 添加新的Entry值 addEntry(hash, key, value, i); return null; } // 特殊处理key=null的值,将其放在table[0]下 private V putForNullKey(V value) { for (Entry<K, V> 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; } private void putForCreate(K key, V value) { int hash = null == key ? 0 : hash(key); int i = indexFor(hash, table.length); for (Entry<K, V> e = 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<? extends K, ? extends V> m) { for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) putForCreate(e.getKey(), e.getValue()); } // 扩容方法,多线程不安全,其他方法也是多线程不安全,但是resize问题比较严重 // 可能形成环形链表,造成死循环,CPU100% void resize(int newCapacity) { // 暂存旧的table Entry[] oldTable = table; // 暂存旧table长度 int oldCapacity = oldTable.length; // 假如旧的table长度等于最大容量,将不做扩容操作 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 根据新容量,新建新table Entry[] newTable = new Entry[newCapacity]; // 执行table上面值得转移,将oldTable上面的值转移到newTable,会重新hash并取模来判断新的下标值 // 多线程环境下,下面方法可能会产生环形链表,造成死循环 transfer(newTable, initHashSeedAsNeeded(newCapacity)); // 将newTable 替换为table table = newTable; // 重新计算阈值 ,控制阈值不能超过MAXIMUM_CAPACITY+1 threshold = (int) Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } // 转移值 void transfer(Entry[] newTable, boolean rehash) { // 获取新的容量 int newCapacity = newTable.length; // 遍历table的Entry for (Entry<K, V> e : table) { // 遍历链表(如果是链表的话) while (null != e) { // 假如线程A取到next,然后时间片用完,CPU切换到线程B继续执行链表值转移操作,并执行完毕 // 那么线程A有了CPU时间片后,继续执行,此时再触发转移值操作,很容易会形成环形链表,造成死循环 Entry<K, V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } // 根据hash值取模,得到在新table的下标值 int i = indexFor(e.hash, newCapacity); // 采用头插法,将当前节点下一个节点指向newTable当前下标的值 e.next = newTable[i]; // 将当前节点作为newTable的头结点 newTable[i] = e; // 将下一个节点赋值给下一轮循环的结点,继续往下执行循环 e = next; } } } public void putAll(Map<? extends K, ? extends V> m) { int numKeysToBeAdded = m.size(); if (numKeysToBeAdded == 0) return; if (table == EMPTY_TABLE) { inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold)); } if (numKeysToBeAdded > threshold) { int targetCapacity = (int) (numKeysToBeAdded / loadFactor + 1); if (targetCapacity > MAXIMUM_CAPACITY) targetCapacity = MAXIMUM_CAPACITY; int newCapacity = table.length; while (newCapacity < targetCapacity) newCapacity <<= 1; if (newCapacity > table.length) resize(newCapacity); } for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) put(e.getKey(), e.getValue()); } // 移除key方法 public V remove(Object key) { // 移除key对应的Entry Entry<K, V> e = removeEntryForKey(key); return (e == null ? null : e.value); } // 移除key对应的Entry final Entry<K, V> removeEntryForKey(Object key) { if (size == 0) { return null; } // 根据hash值和table长度取模,计算当前key所在table的下标 int hash = (key == null) ? 0 : hash(key); int i = indexFor(hash, table.length); // 获取下标所在头结点 Entry<K, V> prev = table[i]; Entry<K, V> e = prev; // 遍历链表 while (e != null) { Entry<K, V> next = e.next; Object k; // 假如hash值相等 && key的equals方法也相同,则将执行移除 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { // 结构变化自增 modCount++; // 大小减1 size--; // 判断当前key是否为头结点,如果是,需要将next做为新的头结点,放在table[i]下 // 为什么说prev==e就是key为头结点呢,因为prev==e只有在第一次判断的时候才会相等,下面的prev=e和e=next会改变其值了 if (prev == e) table[i] = next; // 当前key对应的值非头结点,则将当前结点的next赋值给上个结点的next,因为准备删除当前结点 else prev.next = next; // 空方法,忽略 e.recordRemoval(this); return e; } // 给下一轮循环的结点赋值,向下遍历结点 prev = e; e = next; } return e; } final Entry<K, V> removeMapping(Object o) { if (size == 0 || !(o instanceof Map.Entry)) return null; Map.Entry<K, V> entry = (Map.Entry<K, V>) o; Object key = entry.getKey(); int hash = (key == null) ? 0 : hash(key); int i = indexFor(hash, table.length); Entry<K, V> prev = table[i]; Entry<K, V> e = prev; while (e != null) { Entry<K, V> next = e.next; if (e.hash == hash && e.equals(entry)) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; } public void clear() { modCount++; Arrays.fill(table, null); size = 0; } 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; } 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 Object clone() { HashMap<K, V> result = null; try { result = (HashMap<K, V>) super.clone(); } catch (CloneNotSupportedException e) { // assert false; } if (result.table != EMPTY_TABLE) { result.inflateTable(Math.min( (int) Math.min( size * Math.min(1 / loadFactor, 4.0f), // we have limits... HashMap.MAXIMUM_CAPACITY), table.length)); } result.entrySet = null; result.modCount = 0; result.size = 0; result.init(); result.putAllForCreate(this); return result; } // 键值对,其在维护key和value的同时,还会维护next(下一个Entry)和hash(key的hash值) static class Entry<K, V> implements Map.Entry<K, V> { final K key; V value; Entry<K, V> next; int hash; Entry(int h, K k, V v, Entry<K, V> 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 Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); } public final String toString() { return getKey() + "=" + getValue(); } void recordAccess(HashMap<K, V> m) { } void recordRemoval(HashMap<K, V> m) { } } // 添加新的键值对 void addEntry(int hash, K key, V value, int bucketIndex) { // 判断当前容器大小是否大于等于阈值 && 当前table下标的值存在值,将进行扩容操作 if ((size >= threshold) && (null != table[bucketIndex])) { // 扩容为当前table长度的两倍 resize(2 * table.length); // 重新计算扩容后,当前key将位于table的哪个下标 hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } // 创建键值对 createEntry(hash, key, value, bucketIndex); } // 创建键值对 void createEntry(int hash, K key, V value, int bucketIndex) { // 获取指定下标的元素,作为新结点的next,然后将新结点作为table[index]的头结点 Entry<K, V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); // 大小+1 size++; } // Hash迭代器,KeyIterator和ValueIterator的父类 private abstract class HashIterator<E> implements Iterator<E> { Entry<K, V> next; // 需要返回的下一个Entry int expectedModCount; // 快速失败的判断依据,如果该值不等于HashMap的modCount,将抛出ConcurrentModificationException int index; // 当前迭代下标 Entry<K, V> current; // 当前Entry HashIterator() { // 将HashMap结构修改次数保存一个副本,下面迭代的时候,如果两个值不相等 // 将抛出并发修改异常(其实不太准确,下面案例分析)ConcurrentModificationException expectedModCount = modCount; if (size > 0) { // 遍历table,直到定位到table不为null的值 Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } } public final boolean hasNext() { return next != null; } final Entry<K, V> nextEntry() { // 判断在执行迭代器期间,HashMap有没有对结构进行修改 if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K, V> e = next; if (e == null) throw new NoSuchElementException(); if ((next = e.next) == null) { Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e; } public void remove() { if (current == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } } private final class ValueIterator extends HashIterator<V> { public V next() { return nextEntry().value; } } private final class KeyIterator extends HashIterator<K> { public K next() { return nextEntry().getKey(); } } private final class EntryIterator extends HashIterator<Map.Entry<K, V>> { public Map.Entry<K, V> next() { return nextEntry(); } } Iterator<K> newKeyIterator() { return new KeyIterator(); } Iterator<V> newValueIterator() { return new ValueIterator(); } Iterator<Map.Entry<K, V>> newEntryIterator() { return new EntryIterator(); } // 键值对集合 private transient Set<Map.Entry<K, V>> entrySet = null; // 获取key集合 public Set<K> keySet() { Set<K> ks = keySet; return (ks != null ? ks : (keySet = new KeySet())); } // key集合类,特点:其成员本身不保存元素值,而是在每次迭代的时候,去取HashMap的table上的值 private final class KeySet extends AbstractSet<K> { public Iterator<K> iterator() { return newKeyIterator(); } public int size() { return size; } public boolean contains(Object o) { return containsKey(o); } public boolean remove(Object o) { return HashMap.this.removeEntryForKey(o) != null; } public void clear() { HashMap.this.clear(); } } // 获取值得集合 public Collection<V> values() { Collection<V> vs = values; return (vs != null ? vs : (values = new Values())); } // value集合类,特点:其成员本身不保存元素值,而是在每次迭代的时候,去取HashMap的table上的值 private final class Values extends AbstractCollection<V> { public Iterator<V> iterator() { return newValueIterator(); } public int size() { return size; } public boolean contains(Object o) { return containsValue(o); } public void clear() { HashMap.this.clear(); } } public Set<Map.Entry<K, V>> entrySet() { return entrySet0(); } private Set<Map.Entry<K, V>> entrySet0() { Set<Map.Entry<K, V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); } // 键值对集合类 private final class EntrySet extends AbstractSet<Map.Entry<K, V>> { public Iterator<Map.Entry<K, V>> iterator() { return newEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<K, V> e = (Map.Entry<K, V>) o; Entry<K, V> candidate = getEntry(e.getKey()); return candidate != null && candidate.equals(e); } public boolean remove(Object o) { return removeMapping(o) != null; } public int size() { return size; } public void clear() { HashMap.this.clear(); } } // 序列化 private void writeObject(java.io.ObjectOutputStream s) throws IOException { // Write out the threshold, loadfactor, and any hidden stuff s.defaultWriteObject(); // Write out number of buckets if (table == EMPTY_TABLE) { s.writeInt(roundUpToPowerOf2(threshold)); } else { s.writeInt(table.length); } // Write out size (number of Mappings) s.writeInt(size); // Write out keys and values (alternating) if (size > 0) { for (Map.Entry<K, V> e : entrySet0()) { s.writeObject(e.getKey()); s.writeObject(e.getValue()); } } } private static final long serialVersionUID = 362498820763181265L; // 反序列化 private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // Read in the threshold (ignored), loadfactor, and any hidden stuff s.defaultReadObject(); if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new InvalidObjectException("Illegal load factor: " + loadFactor); } // set other fields that need values table = (Entry<K, V>[]) EMPTY_TABLE; // Read in number of buckets s.readInt(); // ignored. // Read number of mappings int mappings = s.readInt(); if (mappings < 0) throw new InvalidObjectException("Illegal mappings count: " + mappings); // capacity chosen by number of mappings and desired load (if >= 0.25) int capacity = (int) Math.min( mappings * Math.min(1 / loadFactor, 4.0f), // we have limits... HashMap.MAXIMUM_CAPACITY); // allocate the bucket array; if (mappings > 0) { inflateTable(capacity); } else { threshold = capacity; } init(); // Give subclass a chance to do its thing. // Read the keys and values, and put the mappings in the hashmap1_7.HashMap for (int i = 0; i < mappings; i++) { K key = (K) s.readObject(); V value = (V) s.readObject(); putForCreate(key, value); } } // 返回容量 int capacity() { return table.length; } // 返回负载因子 float loadFactor() { return loadFactor; } }
https://gitee.com/lao-dubbo/source-code
- 点赞
- 收藏
- 分享
- 文章举报
- jdk 1.7 HashMap源码分析
- jdk 1.7 1.8 HashMap 源码分析
- java集合HashMap源码分析(JDK1.7)
- HashMap源码分析(JDK1.7)
- HashMap源码分析_JDK1.8版本
- JDK1.7 HashMap源码分析
- 记HashMap源码分析(jdk1.7)
- hashMap源码分析JDK1.7
- JDK1.8版本HashMap的源码分析
- jdk1.6 的 HashMap 源码分析及1.7,1.8的主要更改
- 源码分析系列1:HashMap源码分析(基于JDK1.8)
- HashMap源码分析(jdk1.8)
- HashMap源码分析——JDK1.8
- HashMap源码分析(基于jdk1.6)
- JDK1.8 HashMap源码分析
- 深入理解JDK1.7版本HashMap的扩容机制
- HashMap_jdk1.8源码分析
- java集合(4):HashMap源码分析(jdk1.8)
- HashMap源码分析-基于JDK1.8
- 【集合框架】JDK1.8源码分析之HashMap(一)