/**
* Hash table based implementation of the Map interface. This
* implementation provides all of the optional map operations, and permits
* null values and the null key. (The HashMap
* class is roughly equivalent to Hashtable, except that it is
* unsynchronized and permits nulls.) This class makes no guarantees as to
* the order of the map; in particular, it does not guarantee that the order
* will remain constant over time.
*/
大致意思是Hash Table是基于Map接口实现的,并且HashMap容许null key和null value,HashMap和HashTable很类似,除了HashMap不是线程安全的和允许key和value为空外,并且他不能保证随着时间的推移,顺序保持不变;
可以看到HashMap和HashTable分别插入null的情况;
紧接着是下一段注释:
/**An instance of HashMap has two parameters that affect its
* performance: initial capacity and load factor. The
* capacity is the number of buckets in the hash table, and the initial
* capacity is simply the capacity at the time the hash table is created. The
* load factor is a measure of how full the hash table is allowed to
* get before its capacity is automatically increased. When the number of
* entries in the hash table exceeds the product of the load factor and the
* current capacity, the hash table is rehashed (that is, internal data
* structures are rebuilt) so that the hash table has approximately twice the
* number of buckets.
*/
大致意思是HashMap有两个影响其性能的参数:初始容量和负载因子,其中初始容量的意思是HashMap中初始化的时候的容量大小,负载因子的表示一个散列表的空间的使用程度,当哈希表中的数量超过load factor和当前容量的乘积时,哈希表将被rehash(即重新构建内部数据结构);
所以负载因子越大则散列表的装填程度越高,也就是能容纳更多的元素,元素多了,链表大了,所以此时索引效率就会降低。
反之,负载因子越小则链表中的数据量就越稀疏,此时会对空间造成烂费,但是此时索引效率高。
/**
* Constructs an empty HashMap with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
通过源码可以看到初始容量大小为16,负载因子为0.75,因此当HashMap的大小为12的时候,会出发rehash;
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
Node[] tab; Node p; int n, i; //定义Node数组,p表示tab上的其中一个Node节点,n表示tab的长度,i表示tab的下标。
if ((tab = table) == null || (n = tab.length) == 0) //判断当前的tab中是否为空,或者数组的长度是否为空
n = (tab = resize()).length; //如果为空,则调用resize方法初始化数组,默认长度为16
if ((p = tab[i = (n - 1) & hash]) == null) //(n - 1) & hash 计算出的值作为tab的下标i,并另p为tab[i],也就是该链表第一个节点的位置。并判断p是否为null。
tab[i] = newNode(hash, key, value, null); //如果p为null,这表示tab[i]上没有任何元素,则根据key和value创建一个
else { //产生hash冲突
Node e; K k;
if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))) //如果参数中的hash值和原有的hash值相等,并且参数中的key和原有的key相等
e = p; //则将原有的值付给节点e上
else if (p instanceof TreeNode) //如果存在的值,它的类型是一棵树
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); //则调用putTreeVal方法
else { //如果他既不是一棵树,key不相同,则它是链表
for (int binCount = 0; ; ++binCount) { //遍历链表
if ((e = p.next) == null) { //如果next为空,则表明后面没有元素了,只需把新的节点赋给p.next
p.next = newNode(hash, key, value, null); //则创建新的节点赋值给已有的next属性
if (binCount >= TREEIFY_THRESHOLD - 1) //插入成功后,判断是否需要转为红黑树,这里减一是因为上面bincount还没有自增,因此这里需要减一进行判断
treeifyBin(tab, hash); //如果链表长度达到了8,数组长度小于64,那么就resize调整大小,如果大于64,则转化为红黑树
break;
}
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) //如果hash值和next的hash值相同并且且key也相同,则跳出循环,因为这时候不需要插入,只需替换即可
break;
p = e; //将next 值赋给 p,为下次循环做铺垫
}
}
//如果e部位null,则表示该元素存在
if (e != null) { // existing mapping for key //源代码的注释解释很清楚,就是对已存在的可以做处理
V oldValue = e.value; //取出该元素的值
if (!onlyIfAbsent || oldValue == null) //如果onlyIfAbsent这个参数是true,就不改变已有的值,如果是false则改变,默认该参数为false
e.value = value; //将已有的值替换掉
afterNodeAccess(e); //这个函数不作任何处理
return oldValue; //这里竟然返回的是oldvalue,因为使用put方法的时候很少关注他的返回值,甚至在不看源码的时候都不知道put方法竟然有返回值
}
}
//如果e为空
++modCount; //为了提供给后面的foreach和迭代器所使用
if (++size > threshold) //如果数组长度大于了阀值
resize(); //进行扩容
afterNodeInsertion(evict); //这个函数不作任何处理
return null; //返回null
}
限于篇幅原因,在put方法里面调用到的其他方法就不细说了,有兴趣的同学也可以自己去研究一下。