1、概述
hashmap是集合框架中比较常用的集合,它实现了map接口,根据key的 hash 值存储数据,具有很快的访问速度(数组的存在),可以存储null key和null value,null key只能存储一个,并且存储在数组的第一个下标中。不支持线程同步。HashMap 是无序的,即不会记录插入的顺序。
2、实现
在jdk1.8中,使用了懒加载,所以当我们实例化一个hashmap,他并不会立马初始化数组。只有当使用put方法添加第一个键值对的时候,底层会初始化一个大小为16名为table的Node类型的数组,之后会进行key的两次hash,得到一个hash值。使用此hash值再通过某种算法(hash & n-1))得到数组下标的位置。如果下标中不存在元素,则直接插入成功。如果数组下标有元素,就存在hash冲突。此时就要进行两个hash的比较,如果hash不相同,则会插入成功。如果hash相同,则会使用equals方法进行比较,如果不相同,则插入成功。如果相同,此时put方法就是一个修改操作。随着put操作元素越来越多,当达到某一个阈值(容量*加载因子)数组就会2倍的扩容。当某一个数组下标的链表的节点个数大于8并且数组容量大于64的时候,链表的效率较低,链表就会进行树化,提高检索的效率。链表的时间复杂度为O(n),红黑树时间复杂度为O(logn)。当红黑色的节点个数小于6时,红黑树就会退化成链表,因为小于6时,链表检索效率并不是很低,并且同一个节点存储数据,红黑树占用存储空间大于链表的占用的空间,具体来说是2倍。
3、源码
3.1、构造
//使用懒加载
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
3.2 put操作
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//两次hash操作
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//tab为数组,p为节点,n为数组长度,i为数组的下标
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果tab为空或者tab长度为0
if ((tab = table) == null || (n = tab.length) == 0)
//实例化一个长度为16的数组
n = (tab = resize()).length;
// tab[i = (n - 1) & hash])计算数组下标
// 如果下标为null
if ((p = tab[i = (n - 1) & hash]) == null)
//创建一个Node类型的节点,使用尾插法,next为null
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//第一个节点的hash和刚才的两次hash是否相等
//第一个节点的kye和刚才的key是否相等
//key不为空并且key是否等于第一个节点的key
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//e为第一个节点
e = p;
//判断是否是一个树节点(Node链表 和 TreeNode红黑树)
else if (p instanceof TreeNode)
//如果树结点中,有key重复的,就返回那个重复的结点用e接收,即e!=null
//如果树结点中,没有key重复的,就把新结点放到树中,并且返回null,即e=null
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//不是树节点,key不重复,binCount记录了数组索引的节点个数
for (int binCount = 0; ; ++binCount) {
//使用尾插,寻找最后一个节点
if ((e = p.next) == null) {
//最后一个节点next指向新节点
p.next = newNode(hash, key, value, null);
//当binCount>=7
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//先会考虑扩容,在会考虑树化,就是树化的条件
treeifyBin(tab, hash);
break;
}
//还在循环中,如果hash相等,key重复了
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//快速失败机制
++modCount;
//如果数组容量大于阈值,进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}