HashMap是java中用来存放多组键值对的一种数据结构,该类不保证映射的顺序。本文主要分析HashMap的源码。
1.HashMap中的属性
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
//默认初始化容量为16
static final int DEFAULT_INITIAL_CAPACITY = 16;
//
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//Entry数组
transient Entry[] table;
//存放元素个数
transient int size;
//临界值 加载因子*容量
int threshold;
//加载因子
final float loadFactor;
//修改次数
transient volatile int modCount;
2.HashMap的创建
HashMap根据默认初始化容量(DEFAULT_INITIAL_CAPACITY=16),默认加载因子(DEFAULT_LOAD_FACTOR = 0.75f)创建一个初始化容量大小的Entry数组(Entry[] table),当使用指定初始化容量的构造方法创建HashMap时,会使用能容纳指定大小的最邻近的2的n次幂的容量去创建数组(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);
int capacity = 1;
//只要容量小于你要分配的容量,就左移,直到可以容纳,结果为2幂
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
3.get方法
HashMap通过get()方法取值时,如果key为null,调用getForNullKey()到table[0]位置上查找。根据要取的entry的key的hashCode()计算hash值,到指定位置去取(Entry<K,V> e = table[indexFor(hash, table.length)])。如果要取的位置存放多个entry,遍历该entry链,直到找到(一个entry的hash值和要查询的key的hash值相同 且(key的equals()相同或==)),返回查询到的元素,否则返回null。
public V get(Object key) {
//key为null,调用getForNullKey()
if (key == null)
return getForNullKey();//(1)
//根据key的hashCode值,调用hash方法计算hash值
int hash = hash(key.hashCode());//(2)
//由hash值得到entry下标,遍历entry链
for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null; e = e.next) {//(3)
Object k;
//如果有一个entry的key和要查找的key“相同”,返回value
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
(1)处调用了getForNullKey()方法,用来处理key为null时应该返回的value,方法如下
private V getForNullKey() {
//如果key为null,hash值计算出来的结果为0,所以只要遍历table[0]
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
(2)处调用了hash方法,该方法根据key的hashCode返回值计算hash值
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
(3)处调用了indexFor方法,该方法根据hash值确定Entry在数组中存储的下标
static int indexFor(int h, int length) {
return h & (length-1);
}
4.put方法
在将键值对存入Map时,会根据key对象中重写的hashCode()值,调用HashMap中的hash()方法,根据得到的hash值确定该键值对在HashMap中的存储位置。
遍历当前位置的entry链,如果找到key和要存入元素key相同的entry,替换value并返回oldVaule。否则调用addEntry(hash, key, value, i);存入要存入的元素,并指向原先的entry。
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
//遍历Entry链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果发现key相同,则替换value,并返回oldValue
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方法,将要存储的键值对放到Entry链里
addEntry(hash, key, value, i);//(4)
return null;
}
(4)处调用了addEntry方法,该方法主要用来添加一个新的Entry
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
//在当前下标处,创建一个新的Entry对象,并指向之前的Entry
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
//如果数组大小超出临界值,扩容为容量的2倍
if (size++ >= threshold)
resize(2 * table.length);//()
}
以上代码在创建Entry对象时,调用了Entry类的构造方法,Entry类是HashMap的一个内部类,主要用来封装同一下标处的Entry链。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;//一个Entry链表的一个结点指向的next仍为Entry
final int hash;
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;}
当调用addEntry方法时,如果数组大小超出了临界值,调用resize方法对数组重新扩容,resize方法如下
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
//最大容量也只能是Integer.MAX_VALUE
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);//将所有元素复制到新的数组中,并重新散列,给每个元素分配位置
table = newTable;//赋给当前table
threshold = (int)(newCapacity * loadFactor);//更新临界值
}
transfer方法如下
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
//计算新的下标
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}