一、HashMap中的方法都属于异步操作(非线程安全),允许保存有null数据;
HashMap内部包含了一个Entry类型的数据table:
transient Entry[] table;
Entry存储着键值对,它包含了四个字段,hashcode、k、v、next。从next字段我们可以看出Entry是一个链表。即数组中的每个位置被当成一个桶,一个桶存放一个链表。HashMap 使用拉链法来解决冲突,同一个链表中存放哈希值和散列桶取模运算结果相同的 Entry。
![](https://img.haomeiwen.com/i15192779/42e79268db8af386.png)
拉链法的工作原理:
HashMap<String, String> map = new HashMap<>();
map.put("K1", "V1");
map.put("K2", "V2");
map.put("K3", "V3");
1、新建一个 HashMap,默认大小为 16;
2、插入 <K1,V1> 键值对,先计算 K1 的 hashCode 为 115,使用除留余数法得到 所在的桶下标 115%16=3。
3、插入 <K2,V2> 键值对,先计算 K2 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6。
4、插入 <K3,V3> 键值对,先计算 K3 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6,插在 <K2,V2> 前面。
应该注意到链表的插入是以头插法方式进行的,例如上面的 <K3,V3> 不是插在 <K2,V2> 后面,而是插入在链表头部。
查找需要分成两步进行:
1、计算键值对所在的桶;
2、在链表上顺序查找,时间复杂度显然和链表的长度成正比。
put 操作
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
// 键为 null 单独处理
if (key == null)
return putForNullKey(value);
int hash = hash(key);
// 确定桶下标
int i = indexFor(hash, table.length);
// 先找出是否已经存在键为 key 的键值对,如果存在的话就更新这个键值对的值为 value
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;
}
HashMap 允许插入键为 null 的键值对。但是因为无法调用 null 的 hashCode() 方法,也就无法确定该键值对的桶下标,只能通过强制指定一个桶下标来存放。HashMap 使用第 0 个桶存放键为 null 的键值对。
从 JDK 1.8 开始,一个桶存储的链表长度大于 8 时会将链表转换为红黑树。
二、HashTable中的方法都属于同步操作(线程安全),不允许保存有null数据。
网友评论