public V get(Object key) {
// 如果key为空,用get空key方法处理
if (key == null)
return getForNullKey();
// 计算key的hash值
int hash = hash(key.hashCode());
// indexfor算出hash桶(就是table数组)的索引,遍历这个位置的entry链表
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
// 如果entry的hash值等于key的hash值(这里跟entry这个静态内部类的hash属性算法有关,应该就是根据key算的),key也相等,equals返回ture,那么就认为找到了这个entry,返回entry.value
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
================================================
final Entry<K,V> removeEntryForKey(Object key) {
// 依旧是根据key算hash值再根据hash值和数组长度算出索引值,如果key是null,则索引就是0
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length);
// prey是该索引位置的链表的第一个节点
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
// 开始遍历链表
while (e != null) {
Entry<K,V> next = e.next;
Object k;
// 如果各种都相等,那么就认为找到了给定的key对应的entry
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
// 结构变化指标modCount加1,key-value数量size减1
modCount++;
size--;
if (prev == e)
table[i] = next;
// 将上一个节点prev的next指向当前节点的next,即跳过了当前节点
// 此时当前节点没有任何引用指向,它在程序结束之后就会被gc回收
else
prev.next = next;
e.recordRemoval(this);
return e;
}
// prev在遍历的尾部被赋值当前节点,也就是在下一次遍历中它代表上一个节点
// 主要是为了删除
prev = e;
e = next;
}
return e;
}
这三个方法基本是hashmap的核心方法,可以看出hashmap主要是基于一个entry[],entry本身是一个链表,也就是说这个结构就是一个链表数组(在1.8中,为了避免链表过长,默认是8的长度链表时,构造红黑树替代链表结构),数组的索引用key的hashcode,在加上hash算法,再和(table.length-1)作按位与运算得出,这个大的hash算法尽量避免了hash碰撞,但是仍旧会出现key值不同,但是索引相同的情况,这时就依次往该索引位置的链表中放入新的entry,值得注意的是,这个放入过程是从前面插入,newentry.next = oldentry,一目了然。
在前面一篇对put方法进行分析的时候,发现当size大于threshold(阈值)时,初始的table长度为16,threshold=length*loaderfactor(负载因子默认0.75) =12,table会扩容为原来的两倍,直到达到数组的最大长度1<<30(2的30次方),如果size大于这个值,那么就直接修改为Integer.MAX_VALUE