HashMap:哈希表
JDK1.8
HashMap的定义
1 | public class HashMap<K,V> extends AbstractMap<K,V> |
- 以键值对<K,V>方式存储
- 实现了Cloneable,支持clone()
- 实现了java.io.Serializable,支持序列化操作
一步一步对HashMap进行分析
HashMap的一些属性
1 | /** |
HashMap的静态内部类Node
1 | /** |
HashMap的hash()方法
1 | /** |
HashMap的构造函数
1 | /** |
在有参构造中,this.threshold = tableSizeFor(initialCapacity)
,为什么赋值给threshold?请注意,在构造方法中,并没有对table这个成员变量进行初始化,table的初始化被推迟到了put方法中,在put方法中会对threshold重新计算。
HashMap的putMapEntries()方法,在Map类型的有参构造中用到
1 | /** |
HashMap的resize()方法
1 | /** |
HashMap的putVal()方法
1 | /** |
JDK1.7与JDK1.8
不同之处 | JDK1.7 | JDK1.8 |
---|---|---|
存储结构 | Entry类+数组+链表 | Node类/TreeNode类+数组+链表/红黑树 |
hash的计算 | 较复杂,使用了9次扰动处理=4次位运算+5次异或 | 较简单,使用了2次扰动处理=1次位运算+1次异或 |
扩容时的元素转移 | 重新计算hash,不保证元素还在原位,效率低 | 只计算存放位置,放在原位置/原位置+oldCap |
转移方式 | 头插法,逆序,链表死循环 | 尾插法,不逆序&&不死循环 |
有关HashMap的一些问题
为什么容量必须是2^n?
在计算数组下标时,对2^n-1操作时它的二进制全为1,然后用hash对2^n-1进行按位与操作能快速得出数组下标,并且分布均匀。
链表->红黑树的阈值为什么是8而不是20?为什么不是7?
- 链表中元素的个数符合泊松分布,有8个元素的概率非常低,因此选择了8。
- 防止链表<->红黑树(红黑树->链表的阈值为6)之间出现频繁转换,效率降低。
如何解决hash冲突?
- 好的hash算法:hashCode() + 扰动处理(位运算+异或)
- 扩容机制:>=阈值时进行扩容
- 数据结构:1.7中的数组 + 链表,1.8中的数组 + 链表/红黑树
- 数据存储机制:发生冲突时,1.7采用拉链法 + 头插法,1.8采用拉链法 + 尾插法 + 红黑树
HashMap具备下述特点:
- 键-值(key-value)都允许为空
- 线程不安全、不保证有序、存储位置随时间变化。