Jdk1.7 HashMap源码分析解读
- HashMap类图
- HashMap 成员变量
- HashMap四个构造函数
- 主要方法详解
- public V put(K key, V value)方法
- 初始化数组方法private void inflateTable(int toSize)
- 克隆和Map参数构造器中使用的关键方法private void putAllForCreate(Map<? extends K, ? extends V> m)方法
- HashMap扩容方法void addEntry(int hash, K key, V value, int bucketIndex)
- HashMap解决hash冲突方法void createEntry(int hash, K key, V value, int bucketIndex)
- 总结
HashMap类图
根据类图可知,HashMap实现了三个接口继承了一个抽象类。
实现的接口概览
- 接口如下:
-
java.util.Map
其中Map接口中定义了Map基本操作方法,详细接口描述请参考java.util.Map接口描述.
Map接口中采用一个内部接口java.util.Map.Entry来封装每一个键值对,这样Map中的元素就变成了Map.Entry<K,V>的映射项。 -
java.lang.Cloneable
实现此接口可以对HashMap进行克隆, HashMap默认为浅克隆.
更多了解请点击:
HashMap的clone方法(浅克隆)
HashMap对象的深层克隆
继承的抽象类概览
- 抽象类如下
-
java.util.AbstractMap
AbstractMap 是 Map 接口的的实现类之一,也是 HashMap, TreeMap, ConcurrentHashMap 等类的父类。
Java 集合深入理解(15):AbstractMap
HashMap 成员变量
HashMap定义了13个成员变量。
成员变量1:DEFAULT_INITIAL_CAPACITY
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
此代码段定义HashMap初始容量大小为16,必须为2的幂,源码中使用了移位运算符计算出HashMap的初始容。
问题1: 为什么源码中不直接写成16而要使用移位运算呢?.
答案1: 我的理解是因为操作系统最终会使用二进制进行计算,这样写省略了转换过程,提高了效率。
问题2: 为什么初始容量大小是16,而不是5(奇数代表),8(2的3次幂代表),10(偶数代表)?.
答案2: 待补充
成员变:2:MAXIMUM_CAPACITY
/**
* 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;
定义HashMap最大容量,如果使用者指定大于这个值,HashMap会使用该容量。1<<30 为2 ^ 30=1073741824。
问题1: 那么为什么定义是2^30 ?.
答案1: 待补充
成员变:3:DEFAULT_LOAD_FACTOR
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
HashMap默认的负载因子,用于扩容。
问题1: 那么为什么定义是0.75f ?.
答案1: 待补充
成员变量4:EMPTY_TABLE
/**
* An empty table instance to share when the table is not inflated.
*/
static final Entry<?,?>[] EMPTY_TABLE = {};
定义共享的空表实例。
使用static final关键字修饰变量,说明此实例本身虚拟机加载后放入运行时数据区的方法区,全局共享,实例本身不可变,但是因为是容器类型的实例变量,所以容器中的对象是可以被修改的。
成员变量5:table
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
定义HashMap内部数据结构,类型为Entity,长度由容量指定。
成员变量:6:size
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
HashMap容量大小变量
使用transient关键字说明此变量不需要序列化。
成员变量7:threshold
/**
* 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;
HashMap的极限容量,扩容临界点(容量和加载因子的乘积)
成员变量8:loadFactor
/**
* The load factor for the hash table.
*
* @serial
*/
final float loadFactor;
hash table负载因子变量定义
使用final关键字修饰后,赋值一次,赋值后不能再被改变
成员变量9:modCount
/**
* 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 int modCount;
HashMap修改的次数变量定义。
成员变量10:ALTERNATIVE_HASHING_THRESHOLD_DEFAULT
/**
* 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.
*/
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
默认的容器阀值大小。
成员变量11:hashSeed
/**
* A randomizing value associated with this instance that is applied to
* hash code of keys to make hash collisions harder to find. If 0 then
* alternative hashing is disabled.
*/
transient int hashSeed = 0;
哈希种子(hashSeed),这个hashSeed用于优化哈希函数,默认为0是不使用替代哈希算法,但是也可以自己去设置hashSeed的值,以达到优化效果。
成员变量12:entrySet
// Views
private transient Set<Map.Entry<K,V>> entrySet = null;
定义Map.Entry<K,V>集合。
成员变量13:serialVersionUID
private static final long serialVersionUID = 362498820763181265L;
随机生成的序列化ID
HashMap四个构造函数
构造函数1: HashMap()
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
HashMap默认的构造函数,默认容量大小为16,负载因子为0.75,此两个成员变量在上文中有详细描述。
构造函数2:HashMap(int initialCapacity)
/**
* 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.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
可以指定容量大小的HashMap构造,负载因子使用默认的0.75.
构造函数3: HashMap(Map<? extends K, ? extends V> m)
/**
* 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
*/
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
//根据计算出来的扩容阀值初始化table,hashSeed, threshold
inflateTable(threshold);
//将传入的Map数据循环放入此HashMap中
putAllForCreate(m);
}
构造函数4:HashMap(int initialCapacity, float loadFactor)
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
//如果容量小于0,则抛异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//如果设置的容量大于定义的最大容量,则取最大容量值
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//如果负载因子小于0或者是非法值则抛异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
//初始化负载因子和扩容阀值
this.loadFactor = loadFactor;
threshold = initialCapacity;
//空实现,不需关注
init();
}
其他三个构造器都会调用此构造器。
主要方法详解
public V put(K key, V value)方法
将指定的值与此映射中的指定键相关联。 如果映射先前包含键的映射,则替换旧值。源码如下:
/**
* 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>.)
*/
public V put(K key, V value) {
//如果为数组为空,则进行初始化操作。
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
//如果key为空,则将值value方到数组0位置上,如果0位置有值则替换
return putForNullKey(value);
//计算key的hash值
int hash = hash(key);
//根据hash值和存储数据的数组长度计算位置索引
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.equals(k))) {
V oldValue = e.value;
e.value = value;
//HashMap 空实现,可忽略
e.recordAccess(this);
return oldValue;
}
}
//记录修改次数加1
modCount++;
//增加key-value映射到数组中
addEntry(hash, key, value, i);
return null;
}
HashMap put方法流程图
初始化数组方法private void inflateTable(int toSize)
/**
* Inflates the table.
*/
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
//查找大于toSize的最小2的幂数,例如传入的toSize=23,那么capacity为2^6=32
int capacity = roundUpToPowerOf2(toSize);
//根据容量和负载因子计算扩展阈值,当容量达到此阀值时,HashMap进行扩容。
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//初始化EntiEntry<K,V>[] 数组存储大小
table = new Entry[capacity];
//初始化HashSeed
initHashSeedAsNeeded(capacity);
}
克隆和Map参数构造器中使用的关键方法private void putAllForCreate(Map<? extends K, ? extends V> m)方法
//循环遍历传入的Map集合,复制到新的Map中
private void putAllForCreate(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
putForCreate(e.getKey(), e.getValue());
}
/**
* This method is used instead of put by constructors and
* pseudoconstructors (clone, readObject). It does not resize the table,
* check for comodification, etc. It calls createEntry rather than
* addEntry.
*/
private void putForCreate(K key, V value) {
//根据键计算hash值,如果为空则hash值为0
int hash = null == key ? 0 : hash(key);
//根据hash值和表长度计算在数组中的位置。
int i = indexFor(hash, table.length);
/**
* Look for preexisting entry for key. This will never happen for
* clone or deserialize. It will only happen for construction if the
* input Map is a sorted map whose ordering is inconsistent w/ equals.
*/
//1.如果当前存储的数组的位置存在数据,则替换新值
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;
}
}
//2.否则插入数据
createEntry(hash, key, value, i);
}
HashMap扩容方法void 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.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
HashMap解决hash冲突方法void createEntry(int hash, K key, V value, int bucketIndex)
/**
* 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.
*/
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}