源码博客相关博客写了那么多,突然想起来都没有写一篇我们日常开发中最常见的HashMap,今天简单补一下!
HashMap简介:
HashMap
是应用更加广泛的哈希表实现,行为上大致上与 HashTable
一致,主要区别在于 HashMap
不是同步的,支持 null
键和值等。通常情况下,HashMap
进行 put
或者 get
操作,可以达到常数时间的性能,所以它是绝大部分利用键值对存取场景的首选,比如,实现一个用户 ID 和用户信息对应的运行时存储结构。
HashMap经验总结:
(1)HashMap
基于哈希思想,实现对数据的读写。
(2)当我们将键值对传递给put()
方法时,它调用键对象的hashCode()
方法来计算hashcode
,让后找到bucket
位置来储存值对象。
(3)当获取对象时,通过键对象的equals()
方法找到正确的键值对,然后返回值对象。
(4)HashMap
使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。
(5)HashMap
在每个链表节点中储存键值对对象。当两个不同的键对象的hashcode
相同时,它们会储存在同一个bucket
位置的链表中,可通过键对象的equals()
方法用来找到键值对。如果链表大小超过阈值(TREEIFY_THRESHOLD, 8)
,链表就会被改造为树形结构。
HashMap源码分析:
1、HashMap内存储的元素是Entry,并且Entry是按照链表的形式来存储的。
// 用数组来存储,它的原理是每个数组的元素都是一个链表头
transient Entry<K,V>[] table;
Entry的定义如下:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
}
2、HashMap的get(Object key)方法:
public V get(Object key) {
// 对key进行判空
if (key == null)
return getForNullKey();
// 根据key找Entry
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
//计算hash值
int hash = (key == null) ? 0 : hash(key);
// 根据hash值来查找在数组中的下标位置,然后沿着链表进行查找
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
3、 put(key,value)方法:
public V put(K key, V value) {
// 对key进行判空处理
if (key == null)
return putForNullKey(value);
// 计算hash值
int hash = hash(key);
// 查找在数据中的位置,然后沿着链表查找 ,如果有相同的值就覆盖,没有就加一个entry
int i = indexFor(hash, table.length);
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;
}