概念
- HasnMap是基于map接口实现,元素以键值对的方式存储,并且键和值都可以使用null,因为 key不允许重复,因此只能有一个键为null
- HaasnMap是 无序不重复的,而且HashMap是线程不安全 的
- JDK7HashMap的数据结构为:数组+链表
- JDK8HashMap的数据结构为:数组 + 链表 + 红黑树
存储的优点
- 数组的特点:查询效率高,插入和删除效率低
- 链表的特点:查询效率 低,插入和删除效率高
- 在HasnMap底层使用数组加 (链表或红黑树) 的结构完美的解决了数组和链表的问题,使的查询和插入,删除的效率都 很高
- HashMap的散列表是懒加载机制,在第一次put的时候才会创建
HashMap存储元素的过程
首先将k、v封装到Node对象当中(节点)
调用k的hasnCode()方法取出hash值;通过hashcode值和数组长度取模得到元素存储的下标
此时分为两种情况
- 下标位置上没有元素,直接把元素方进入
- 该所以已有元素,判断该位置的元素和当前元素是否相等,使用equals来比较(默认是比较两个对象的地址)。如果两只相等则直接覆盖,如果不等则(Hash碰撞)在原元素下面使用链表的结构存储该元素(如果已存在链表,则插在链表尾部),每个元素节点都有一个next属性指向下一个节点,这就由数组结构变成了数组+链表;因为链表中元素太多的时候回影响查找效率,所以当链表的元素个数达到 8 的时候使用链表存储就转变成了使用红黑树存储(当红黑树上的节点数量小于 6 个,会重新把红黑树变成单向链表数据结构),原因就是红黑树是平衡二叉树,在查找性能方面比聊表要高
HashMap取值的实现
- 先调用k的hashCode()方法得出哈希值,并通过hash算法转换成数组的下标
- 通过hash值转换成数组下标后,通过数组定位到下标位置,如果改位置上什么都没有,范围null;如果该位置上有单向链表,那么就拿参数K和单向链表上的每一个节点的K进行equals比较,如果所有equals都返回false,则返回null,如果有一个节点的K和参数K通过equals返回true,那么此时该节点的value就是要获取的value值
扩容
- HashMap中有两个重要参数,初始容量大小和负载因子,在HashMap刚开始初始化的时候,使用默认的构造方法,会返回一个空的table,并且 thershold(扩容阈值)为 0 ,因此第一次扩容的时候默认值就会是 16 ,负载因子默认为 0.75 ,用数组容量乘以负载因子得到一个值,一旦数组中存储的元素个数超过这个值就会调用rehash方法将数组容量增加到原来的两倍,threshold也会变为原来的两倍
- 在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能。所以,如果知道要存入的数据量比较大的话,可以在创建的时候先指定一个比较大的数据容量
- 也可以引申到一个问题HashMap是先插入还是先扩容:HashMap初始化后首次插入数据时,先发生resize扩容再插入数据,之后每当插入的数据个数达到threshold时就会发生resize,此时是先插入数据再resize
HashMap中的扩容是在元素插入之前进行的扩容还是元素插入之后进行的扩容
在 JDK1.7中是在元素插入 前 进行的扩容,在JDK1.8 中是先加入元素 后 再判断是否进行扩容
存储元素超过阈值一定会进行扩容吗
在 JDK1.7 中不一定,只有存储元素超过阈值并且当前存储位置不为null,才会进行扩容,在 JDK1.8 中会进行扩