HashMap要点

251 阅读2分钟

结构

  • 数组和链表组合构成的数据结构。1.8链表可能升级成红黑树。
  • 数组的每个地方存放了Key-Value的实例。Java7叫Entry,Java8叫Node。
  • put的时候会根据key的hashcode计算一个index值对应数组的位置。
  • put的时候如果计算的index相同则会在index位置形成链表。

新的Node节点如何插入?

计算index

  • 计算inedex:index = HashCode(Key) & (Length- 1)。
  • hashMap容量是2次幂,因为这样length-1的二进制表示的所有位全是1,计算出的index的结果等同于HashCode后几位的值,所以只要输入的hashcode均匀分布,index就是均匀分布的。

JDK1.7头插

  • 新来的值取代原有的值,原有的值推到链表里面。
  • 多线程并发操作会有死循环。

JDK1.8尾插

  • 链表情况下多线程死循环避免死循环。原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。
  • 多了红黑树情况。

什么时候红黑树

链表长度大于8

扩容过程

扩容时机

  • Capacity:HashMap当前长度。
  • LoadFactor:负载因子,默认值0.75f。 比如当前容量100,当存进第76个的时候就需要扩容

扩容过程

  • 扩容:创建一个新的Node数组,长度是原来的2倍。
  • ReHash:遍历原Node数组,把所有Node重新Hash到新的数组。

为什么重写equals()需要重写hashCode()方法

在HashMap中是通过hashCode确定index的位置的。所以相同的对象一定要返回相同的hashCode(),不同的对象返回不同的hashCode();

线程安全的Map

即使1.8HashMap不会出现死循环,但他不是线程安全的,因为它的get/put方法都没有加同步锁。

HasHtable

所有字段全部synchronized,现在一般不用。

Collections.SynchronizedMap

接受一个HashMap参数 把所有方法包装成synchronized,性能跟Hashtable一样。但是好处是可以将非线程安全的 Map 转化为线程安全的 Map。

ConcurrentHashMap

JDK1.7使用分段锁实现Segment + HashEntry + ReentrantLoc。 简单来说,ConcurrentHashMap 是一个 Segment 数组(默认长度为16),每个 Segment 又包含了一个 HashEntry 数组,所以可以看做一个 HashMapSegment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 Segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。

JDK1.8使用 Node数组 + synchronized + volatile cas

为什么JDK1.8要升级成红黑树

待更新