今天来分析一下JDK中的HashMap类的源代码.
HashMap不同于list、set,它又称为关联数组,它会同时存储一对值,让其之间形成一种关联.
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
可以看到HashMap实现了Map接口,Map是集合类中的另外一个接口,它规定了所有map的实现类只能存储键值对
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;//初始化装载因子
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);//初始化容量
table = new Entry[DEFAULT_INITIAL_CAPACITY];//初始化entry数组,用来存放数据
init();//调用init方法,该方法暂未实现
}
默认的构造方法会使用默认值来初始化装载因子、容量、以及底层的entry数组
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);
//capacity必须是小于初始化容量的最大2的冥值
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
该构造方法传入了两个参数,分别是初始化容量、装载因子,其中初始化容量必须是2的冥值
下面分析一下put方法:
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;
}
首先判断一下存放入的key是否为空,若为空,则调用putForNullKey方法:
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {// 循环遍历entry链
if (e.key == null) {//如果key为空,则用带待加的value替换原来的value
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
该方法是一个私有方法,首先取出数组中第0个位置上的entry,而此时可以看到循环条件中有一个e = e.next的条件,明明是数组,那怎么会出现这样的条件呢?其实除了用entry数组存放数据外,在每个数组元素中,都有
一个指向下一个entry的引用.当key相同时.用新的value替代原来的value,最后构造一个entry添加进去entry链中,新添加的entry在整个链的末尾,他的下一个引用指向第0个索引上的entry.
接着分析put方法,上面是key为null时的处理,若key不为空,就根据key的hashcode取得一个hash值,然后计算出该hashcode对应的数组下标,若该下标对应的位置没有数据,则直接构造entry(通过addEntry方法)加入,否则 遍历entry链,
如果正在遍历的entry的key的hash值与待添加的key的hash值相等并且key值相等,则进行value替换.
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
remove方法用了删除entry,底层调用了removeEntryForKey方法:
final Entry<K,V> removeEntryForKey(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());//根据key得到hash值
int i = indexFor(hash, table.length);//根据hash值得到索引下标
Entry<K,V> prev = table[i];//prev代表的i个元素
Entry<K,V> e = prev;//e指向prev
while (e != null) {
Entry<K,V> next = e.next;//next代表下一个entry
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {//如果haah相等并且key值相等
modCount++;
size--;
if (prev == e)//该链表中只剩有一个entry
table[i] = next;//让第i个索引上的entry直接指向next
else
prev.next = next;//让前面的entry的下一个索引指向带删除entry的下一个索引
e.recordRemoval(this);
return e;
}
prev = e;//记录前面的entry
e = next;//e指向下一个entry
}
return e;
}
该方法表示通过key删除entry