结构
- 由数组和链表组合构成的数据结构。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 数组,所以可以看做一个 HashMap, Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 Segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
JDK1.8使用 Node数组 + synchronized + volatile cas
为什么JDK1.8要升级成红黑树
待更新