携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情
HashMap
- hashMap中key不可重复(key可为null,只允许一条记录),value可重复(允许多条记录为null)
- hashMap非线程安全
- hashMap在添加删除的时候,如果两个线程计算的hash值相同,定位entry数组到hash位置,其中一个线程会覆盖另一个线程写入的值,本质上entry数组是竞争资源,没有进行并发处理
Map map = new HashMap();
- 可以用 Collections 的 synchronizedMap 方法使HashMap 具有线程安全的能力,或者使用ConcurrentHashMap 创建
Map map = new HashMap();
Map map1 = Collections.synchronizedMap(map);
Map map2 = new ConcurrentHashMap<>();
HashMap结构
jdk7(数组+链表)
- HashMap 里面是一个数组,然后数组中每个元素是一个单向链表
- 查找具体值的时候根据hash值定位数据具体下标,然后顺着链表比较找到相应的内容,时间复杂度取决于链表长度,为O(n)
jdk8(数组+链表+红黑树)
- 链表中的元素超过8个以后,会将链表转换为红黑树,以加快检索速度,降低时间复杂度为O(logN)
- 当链表长度大于阈值(默认是8)的时候,将链表转换为红黑树,以减少搜索时间,提升检索效率;当红黑树节点小于阈值(默认为6)的时候,将红黑树转换为链表,这样做是为了节省维护红黑树这种数据结构的空间消耗。
索引计算
索引计算方法
-
首先,计算对象的 hashCode()
-
再进行调用 HashMap 的 hash() 方法进行二次哈希
- 二次 hash() 是为了综合高位数据,让哈希分布更为均匀
-
最后 & (capacity – 1) 得到索引
数组容量为何是 2 的 n 次幂
- 计算索引时效率更高:如果是 2 的 n 次幂可以使用位与运算代替取模
- 扩容时重新计算索引效率更高: hash & oldCap == 0 的元素留在原来位置 ,否则新位置 = 旧位置 + oldCap
注意
- 二次 hash 是为了配合 容量是 2 的 n 次幂 这一设计前提,如果 hash 表的容量不是 2 的 n 次幂,则不必二次 hash
- 容量是 2 的 n 次幂 这一设计计算索引效率更好,但 hash 的分散性就不好,需要二次 hash 来作为补偿,没有采用这一设计的典型例子是 Hashtable
扩容问题
1.7 与 1.8 的区别
- 链表插入节点时,1.7 是头插法,1.8 是尾插法
- 1.7 是大于等于阈值且没有空位时才扩容,而 1.8 是大于阈值就扩容
- 1.8 在扩容计算 Node 索引时,会优化
扩容的加载因子默认为0.75f
- 在空间占用与查询时间之间取得较好的权衡
- 大于这个值,空间节省了,但链表就会比较长影响性能
- 小于这个值,冲突减少了,但扩容就会更频繁,空间占用也更多