概述
HashMap是一个存储key-value键值对的集合,每个键值对也叫Entry,每个Entry可以看做是一个链表的节点,每个元素的初始值都是null。
API
put
新插入一个key为“dog”的元素,调用hashMap.put("dog",0):
- 首先利用哈希函数来确定Entry的插入位置,index=Hash(“dog”)
- 放入指定的位置,若该位置无元素,Entry放入该位置,若该位置有元素,及发生hash冲突,执行3。
- 我们的Entry元素的next指针指向该位置存在的Entry对象,可抽象理解为纵向插入链表中。
- 需要注意的是,新来的Entry对象的对象使用的是头插法,因为发明者认为后插入的Entry被查找的可能性更大。
get
查找一个key为“dog”的元素,调用hashMap.get("dog",0):
- get方法根据index=Hash("dog"),找到该位置的索引
- 如果该位置有多个元素Entry,那么顺着对应链表的头结点一个一个找,找到为止。
知识点
- HashMap的初始长度是多少,为什么这么规定?
初始长度是16,每次自动拓展或者手动扩展失,长度都必须是2的幂,之所以这样选择是因为key映射到index的Hash算法
index = HashCode(Key) & (Length - 1),Hash算法最终得到的index结果,完全取决于Key的Hashcode值的最后几位。2的幂-1的二进制都是111111....,index的结果会更加均衡分布,
- HashMap是线程安全的吗?
不是线程安全的,在HashMap随着插入元素达到一定的饱和度时,会进行Resize操作,影响Resize的操作有两个:
- Capacity:HashMap的当前长度。HashMap的长度是2的幂
- LoadFactor:HashMap负载因子,默认值为0.75f。
衡量HashMap是否进行Resize的条件如下:HashMap.Size >= Capacity * LoadFactor
Resize的两个步骤:
- 扩容:创建一个新的Entry数组,长度为原来的二倍。
- ReHash:遍历原Entry数组,把所有的Entry根剧新的Hash算法重新Hash到新数组。
HashMap在设计上很完美,不过存在线程安全问题,在Resize达到临界点时,两个线程同时操作可能出现死锁。
- 高并发情况下,为什么HashMap可能出现死锁?
在Java 8中HashMap的结构有怎么样的优化?
HashMap和HashTable的区别
- HashMap允许存放null key和null value,HashTable不允许存放null的key或者value
- HashMap的index = HashCode(Key) & (Length - 1),HashTable的index = (hash & 0x7FFFFFFF) % tab.length
- HashMap的初始化容量大小和扩容后大小总是2的幂,HashTable初始化容量大小是11,扩容后大小为老容量的二倍加一
- HashMap和HashTable都实现了Map接口,但是HashTable继承了Dictionary,HashMap继承了AbstractMap
参考程序员小灰的公众号