一、HashMap的数据结构
HashMap是一个数组和单向链表的结合体。
- 数组:在查询方面效率很高,随机增删方面效率很低。
- 单向链表:在随机增删方面效率较高,在查询方面效率很低。
二、HashMap底层的实现原理
1. put(key,value)方法
- 第一步:先将key和value封装到Node对象中。
- 第二步:底层会调用key的hashCode()方法得到哈希值,然后通过哈希算法,将哈希值转换为数组的下标。
- 第三步:如果该下标位置上没有任何元素,就把这个Node对象添加到这个位置上;
- 第四步:如果该下标对应的位置上已经有链表了,此时会将要添加的Node对象中的key和链表上每个节点中的key进行equals,如果所有的equals方法返回结果都是false,那么要添加的Node对象就会被添加到链表的末尾。如果其中有一个equals返回了true,那么这个节点的value会被将要添加的Node对象中的value所覆盖。
2. get(key,value)方法
- 第一步:先调用key的hashCode()方法得到哈希值。
- 第二步:通过哈希算法将哈希值转换成数组下标,通过数组下标快速定位到某个链表。
- 第三步:如果没有定位到任何链表,那么返回null。
- 第四步:如果定位到了一个链表,那么会拿着key和链表上的每一个节点的key进行equals,如果所有equals方法返回的都是false,那么get方法返回null,只要其中有一个返回了true,那么这个节点的value就是我们需要的内容,get方法最终返回这个value。
三、为什么哈希表的增删查效率都很高?
因为增删是在链表上完成,查询也不需要扫描整个哈希表,只需要扫描部分哈希表,结合了链表和数组的优点,摒弃了二者的缺点。
//Object中equals方法的源码
public boolean equals(Object obj) {
return (this == obj);
}
//Object中hashCode方法的源码
public native int hashCode();
(1)因为HashMap两个常用的方法put和get都会调用hashCode(),和equals()方法,所以这两个方法都要重写。
(2)如果只重写equals方法,而不重写hashCode方法,那该类new出来的对象,hashcode都会不一样(因为是内存地址),那根据put方法的原理,哈希表就会变成一个一维数组了,这样哈希表就没有存在的意义,无法满足散列分布均匀。而且会违反哈希表中“不可重复”的特点。比如new了两个Person类对象,Person类里面只有一个属性,身份证号码,如果这两个Person对象的身份证号码完全一模一样,我们肯定认为是同一个人,而且equals方法肯定也返回true,但是因为内存地址不一样,如果这两个对象作为key放入哈希表中,因为先调的hashcode方法,由于hashcode必不一样,所以equals不会被调用,所以这两个对象都会放到哈希表里,这不是我们期望的结果。
(3)重写equals要满足几个条件:
①自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
②对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
③传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
④一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
⑤对于任何非空引用值 x,x.equals(null) 都应返回 false。
四、哈希表的其他知识
- 初始化容量
HashMap的默认初始化容量是16。在源码中是这样写的:
/**
* The default initial capacity - MUST be a power of two.
* 默认的初始化容量必须是2的次幂。
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//1向左移四位,本来是1,那么变化后就是10000,就是16
重点:HashMap初始化容量必须是2的次幂,这是SUN推荐的,这是为了达到散列分布均匀,为了提高HashMap的存取效率。
//底层运用到了位运算来代替%运算,效率高。
static int indexFor(int h, int length) {
return h & (length-1);
}
-
默认加载因子
HashMap的默认加载因子是0.75。这个默认加载因子是当HashMap集合底层数组到达总容量的75%的时候,数组开始扩容。 -
哈希表在1.8后的变化
static final int TREEIFY_THRESHOLD = 8;
如果单向链表上的元素超过八个,会把单向链表变成红黑树数据结构。
当红黑树上的节点小于6时,会重新把红黑树变成单向链表数据结构。
- HashMap和HashTable的区别
①HashMap的key和value都可以为null,HashTable都不可以为null。
②HashTable是线程安全的,HashMap不是线程安全的。
③HashTable的初始化容量是11,默认加载因子是0.75f,扩容是原容量* 2 + 1