HashMap算是Java集合框架中使用最普遍的,对于他的基本特性和使用方法都知道了解,但是他的底层实现却很少去了解,也很少关注。最近空闲在网上找了很多关于这方面的帖子和博客,综合总结一下。
https://blog.csdn.net/UzV80PX5V412NE/article/details/78591134
https://www.cnblogs.com/chengxiao/p/6059914.html
一. HashMap的基本概念与结构
HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做entry。而HashMap的基本结构是
数组+单链表。基本特性 : Key,value可以为null , Key不可以重复,重复的话后面的value会覆盖前面的value,非线程安全。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key; V value; Entry<K,V> next;//存储指向下一个Entry的引用,单链表结构
int hash;//对key的hashcode值进行hash运算后得到的值,存储在Entry,避免重复计算
/** * Creates new entry. */
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
}
Entry结构: hash是首地址,根据key的值通过哈希算法(调用更底层语言)得出 hashcode()
public class HashMap<k,v>
extends AbstractMap<k,v>
implements Map<k,v>, Cloneable, Serializable
{
transient Entry<k,v>[] table;//一个Entry数组
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
//遍历当前下标的Entry对象链,如果key已存在则替换
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;
}
}
addEntry(hash, key, value, i);
return null;
}
}
HashMap数组每一个元素的初始值都是Null。
二 .HashMap常用方法
Put()方法:
HashMap数组的每一个元素不止是一个Entry对象,也是一个链表的头节点。每一个Entry对象通过Next指针指向它的下一个Entry节点。当新来的Entry映射到数组的位置有值时,只需要插入到数组对应的位置下的链表即可:
需要注意的是,新来的Entry节点插入链表时,使用的是“头插法”。因为默认后来的会先使用,或者被使用的概率更高。
Get()方法
和put方法差不多,根据key,计算出hash值,然后再循环链表,顺序查找链表的key,是否符合。最后查出对应的value。
1、获取key
2、通过hash函数得到hash值
int hash=key.hashCode();
3、得到桶号(一般都为hash值对桶数求模)
int index =hash%Entry[].length;
4、比较桶的内部元素是否与key相等,若都不相等,则没有找到。
5、取出相等的记录的value。
三 .HashMap的构造
HashMap有几个默认参数
//实际存储的key-value键值对的个数
transient int size;
//阈值,当table == {}时,该值为初始容量(初始容量默认为16);当table被填充了,也就是为table分配内存空间后,threshold一般为 capacity*loadFactory。HashMap在进行扩容时需要参考threshold,后面会详细谈到
int threshold;
//负载因子,代表了table的填充度有多少,默认是0.75
final float loadFactor;
//用于快速失败,由于HashMap非线程安全,在对HashMap进行迭代时,如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作),需要抛出异常ConcurrentModificationException
transient int modCount;
长度默认是16
index = HashCode(Key) & (Length - 1)
哈希算法是将key的hashcode 与 数组长度-1做位运算,这样可以减少碰撞发生的概率(hash值相同)
HashMap中的碰撞探测(collision detection)以及碰撞的解决方法
当两个对象的hashcode相同时,它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用LinkedList存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在LinkedList中。这两个对象就算hashcode相同,但是它们可能并不相等。 那如何获取这两个对象的值呢?当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,遍历LinkedList直到找到值对象。找到bucket位置之后,会调用keys.equals()方法去找到LinkedList中正确的节点,最终找到要找的值对象使用不可变的、声明作final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择。
重写equals()和hashcode()
在重写equals的方法的时候,必须注意重写hashCode方法,同时还要保证通过equals判断相等的两个对象,调用hashCode方法要返回同样的整数值。而如果equals判断不相等的两个对象,其hashCode可以相同