HashMap解析
本文不会对HashMap的源码或者原理什么的进行分析,就针对Java7/8中HashMap的不同之处进行分析。
Java 7中的HashMap
数据结构
HashMap在Java7中的底层结构为Entry对象。它的具体定义如下:
1 | static class Entry<K,V> implements Map.Entry<K,V> { |
具体的数据结构如下:
在Java7中,当成百上千个节点在hash时发生碰撞,这些节点都会存储在一个链表中。如果要查找这个链表中的某一个节点,就要花费O(N)的查找时间。
发生冲突时
在Java 7 中发生冲突的时候,会将新的冲突节点插入到对应链表的头节点位置。这样就会在多线程情况下存在一个死循环的问题。因为,在扩容的时候,原先的链表会逆序将节点插入到新HashMap的链表中去,这种情况下就存在节点循环引用的可能。最后导致get的时候,死循环。
扩容
在扩容resize过程中,采用单链表的头插入方式,在将旧数组上的数据 转移到 新数组上时,转移操作 = 按旧链表的正序遍历链表、在新链表的头部依次插入,即在转移数据、扩容后,容易出现链表逆序的情况 。 多线程下resize()容易出现死循环。此时若(多线程)并发执行 put()操作,一旦出现扩容情况,则 容易出现 环形链表,从而在获取数据、遍历链表时 形成死循环(Infinite Loop),即 死锁的状态 。
Java 8中的HashMap
数据结构
HashMap在Java 8中使用了和ConcurrentHashMap一样的Node节点作为底层存贮的数据结构
1 | static class Node<K,V> implements Map.Entry<K,V> { |
发生冲突时
发生hash冲突后,会优先判断该节点的数据结构式是红黑树还是链表,如果是红黑树,则在红黑树中插入数据;如果是链表,则将数据插入到链表的尾部并判断链表长度是否大于8,如果大于8要转成红黑树。
扩容
由于 JDK 1.8 转移数据操作 = 按旧链表的正序遍历链表、在新链表的尾部依次插入,所以不会出现链表 逆序、倒置的情况,故不容易出现环形链表的情况 ,但jdk1.8仍是线程不安全的,因为没有加同步锁保护。