一、jdk1.7以及以前的HashMap
首先举个例子:假设现在有一所新建的幼儿园,校长要求要保留4个班级,马上要到开学季,要迎来一批新生(相当于向HashMap中添加的元素)来报道,学校会为他们分配班级,学校要保证这批新生被均匀的分配到4个班级(这4个班级相当于HashMap中的hash表)中。于是校长采用一种班级分配策略(相当于HashMap分配元素的hash算法):每个学习按照报名的顺序进行编号1,2,3……,然后用这些学生的编号N,用N除以4的余数来决定班级号。例如:一个学生的编号为5,5/4的余数是1(余数相当于元素在hash表中的位置),那么这个学生就会被分配到1班。这样一来,就能保证每只要学生数量超过4个,都会被均匀的分配到4个班级里面。
这时有人可能会提出用抓阄的形式来分配新生,由于幼儿园是新建的,所以并不知道会有多少新生来报道。如果要是为每个班级规定10个名额,如果只来10个新生,就有可能出现一种极端的情况,这10个新生全部都分配到一个班级里面。
校长一开始预计每个班级不超过20个新生(相当于HashMap源码中的threshold,也是哈西码相同的元素个数上限),因为人数太多,不方便老师管理。但是没想到招生情况乐观,每个班级的人数都超过了20,于是校长为了保证教学质量,扩充班级的个数,假设这次班级扩充为8个班级,这时为了保证所有的学生全部都均匀的分配到这8个班级,前面已经分配好的4个班级的学生也需要进行重新分配(相当于HashMap的源码中resize()过程,也就是hash表的扩容过程),此时还是按照上面提到分配策略去分配,只不过除数由4变成了8,这样一来,学生就会被均匀的分配到8个班级中。
看完这个例子可能会有两个疑问:将之前分配好的4个班级重新分配会不会有些浪费时间?答:确实有些浪费资源和时间,但是没办法,为了保证均匀分配,只能这样。
HashMap的底层数据结构
HashMap作为一个容器,底层使用的就是散列的数据结构。jdk1.8以前的HashMap都是用数组+链表的形式实现的,如下图(图片来自百度)。下图中的table数组相当于学校;数组中的每个位置相当于班级号;数组中的每个元素每个班级第一个来的学生;链表相当于整个班级的学生;Entry<K,V>节点相当于学生;分配元素使用hash算法相当于分配学生的策略。
![](https://i-blog.csdnimg.cn/blog_migrate/fa7b6d46119d64721e87b96afb58a120.png)
二、jdk1.8以及以后的HashMap
jdk1.8以后的HashMap引入了红黑树,使用的是数组+链表+红黑树的形式来实现的,当链表上的元素个数超过了一定个数以后就用红黑树类解决hash冲突。如下图(图片来自百度)。红黑树的引入主要是为了提高元素的查找速度。关于红黑树的介绍可以参考这篇文章
![](https://i-blog.csdnimg.cn/blog_migrate/6d9b85960f091d8396d3da943db625a5.png)