HashMap应该是在javaweb开发里面使用频率最高的几个类之一,使用频率直逼String类。因此也就有必要弄清楚这个类的实现原理,后面如果使用HashMap产生问题的时候分析原因也要从容一些 。
HashMap内部的结构是数组+链表。数组存储着由hash值根据一定的算法计算出的有相同特征值的对象组成的链表。
如下图:
下图表示一个HashMap的结构,其中数组长度是M,虽然各个链表的长度不一定一样,为了方便,统一把长度表示为N。
左侧是数组,存储链表的头部,右侧是所对应的链表的其它节点。
数组存的是各个链表的头部,如果某个链表进来一个新对象,则它将取代原有的头部存储在数组中,原有的头部将后移一位。
下面开始源码的分析:
1.继承关系和实现的接口
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap实现了Cloneable接口,HashMap对象可以调用clone()方法进行自我复制。
HashMap还实现了Serialiable接口,这个是序列化接口,说明HashMap可以进行序列化,序列化后可以在网络上传输。
当然了,HashMap实现了Map接口和继承了AbstractMap抽象类,使其具有Map类的基本功能。
2.类属性
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 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.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 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;
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
/**
* The next size value at which to resize (capacity * load factor).
* @serial
*/
int threshold;
/**
* The load factor for the hash table.
*
* @serial
*/
final float 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).
*/
transient int modCount;
DEFAULT_INITIAL_CAPACITY:Map默认的容量大小。
MAXIMUM_CAPACITY:Map的最大的容量大小。
DEFAULT_LOAD_FACTOR:默认阈值,如果当前HashMap里面的对象数量超过当前容量*当前阈值,则进行扩容。
table:是个Entry数组,储存链表的头部。
size:HashMap所储存的键值对数量。
threshold:当前容量*阈值。
loadFactor:当前阈值。
modCount:用于记录HashMap的修改次数(这个后期再详细解释)。
3.构造方法
/**
* 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) {
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)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
/**
* 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);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
/**
* 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);
putAllForCreate(m);
}
构造方法也比较直观,主要分两类:
1.构造一个空的HashMap,重载的构造方法的参数主要是容量和阈值。
2.用一个已有的HashMap对象生成一个新的对象。
4. 内部类
a.Entry<K,V>
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
Entry是HashMap存储数据的核心,数据对象被加入HashMap中时,实际上是被封装到了Entry对象中。
Entry对象里面不仅存储着对象的key和value还存储着指向链表下一个对象的值和该对象的hash值。
5.常用的方法
a.get(Object key)
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
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.equals(k)))
return e.value;
}
return null;
}
get()方法的实现原理是:先根据对象的hashcode得到数据应该被存储的链表,然后遍历该链表获得value对象。
方法内部还调用了几个方法:
1.getForNullKey():如果可以为空的话,则遍历第一个链表,获取value值。
2.hash()和indexFor()这两个方法一起确定了数据存储的链表。indexFor只有一行:
static int indexFor(int h, int length) {
return h & (length-1);
}
b.put(Object obj)
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
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;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
put()方法实现原理:
先判断key值是否为空,如果是空,则加在第一个链表的后面。否则,根据Hashcode计算hash值进而得到应当被放置的链表。
遍历该链表,判断是否已经存在hash值和key值的都相同的Entry对象。如果存在则覆盖value值,并返回旧的value值,put()方法结束;如果不存在,则将Entry对象挂载到链表的上面。如果Map里面的键值对的数量超过了阈值,则对链表的数量也就是Entry数组的长度进行扩容,将数组的长度扩展为原来的两倍。然后,重新挂载原来的Entry对象,可能会更新其所在的链表。