HashMap源码–(三)put方法
Map内部的数据结构是以key-value键值对的方式存数据。key和value都可以为空。
Map有很多子类,HashMap、LinkedListMap、TreeMap等, HashMap是比较常用的,它的存取速度快,是基于哈希表的Map接口实现。存取数据时是根据哈希算法计算数据存在位置,在相同哈希值计算的位置存放的数据结构是链表。添加元素使用方法put方法,如下。
/**
* 将指定的value和指定的key映射到map中。
* 如果map中包含这个key,则替换。
*
* @param key 一个对象,例如字符,用于映射指定的值
* @param value map中存的值,根据映射的key进行获取的值
* @return 如果map中存在key,则返回被替换的值;如果key不存在
* 则返回null。根据返回值key判断map中是否存在。
*/
public V put(K key, V value) {
//key为null时,添加或替换value
if (key == null)
return putForNullKey(value);
//获取哈希值
int hash = hash(key.hashCode());
//根据哈希值和Entry[] table数组的长度计算出value存放的位置
int i = indexFor(hash, table.length);
//根据数组的位置找到链表的表头
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
//根据哈希值和key查找Entry
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;
}
}
//操作次数加1
modCount++;
//Entry数组为找到key,添加数据
addEntry(hash, key, value, i);
return null;
}
put方法添加或修改数据时,key为null时的添加方法为:
/**
* key为null时添加值
*/
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
key的值决定了值存放在Entry数组的位置,key为null时,存放在Entry数组下标为0的位置;key不为null时,要根据哈希算法和数组的长度计算存放位置,存放位置算法如下:
/**
*计算hash值
*/
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
/**
* 根据数组的长度,计算哈希值h在数组的位置
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
相同的key用hash(int h)方法计算出相同的值。hash方法采用位移算法,这个算法采用高位计算,防止低位不变,高位变化时,hash值冲突问题。
indexFor方法是计算hash值h在数组存放的位置。该方法运用的很巧妙,当length的长度为2的幂指数时,h & (length-1)计算公式相当于对length取模,但&的效率比%高。
假设数组长度分别为15和16,优化后的hash码分别为8和9,那么&运算后的结果如下:
h & (table.length-1) hash table.length-1
8 & (15-1): 0100 & 1110 = 0100
9 & (15-1): 0101 & 1110 = 0100
---------------------------------------------------
8 & (16-1): 0100 & 1111 = 010
9 & (16-1): 0101 & 1111 = 0101
可以看出当长度为15时,8和9对应的值是相同的,长度为16时,值则不相同。可见HashMap的长度为2的幂指数的巧妙,且根据长度获取位置也很高效。
根据数组位置i获取Entry对象, for (Entry<K,V> e = table[i]; e != null; e = e.next)
这个对象Entry e是链表的表头,它的next指向链表的下一个对象。 if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
在链表中匹配hash值和key,进行覆盖。