浅识HashMap

52 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情

image.png

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

  • 在空间占用与查询时间之间取得较好的权衡
  • 大于这个值,空间节省了,但链表就会比较长影响性能
  • 小于这个值,冲突减少了,但扩容就会更频繁,空间占用也更多