1、 基础认知
HashMap是由数组和链表组合构成的数据结构。
默认负载因子 loadFactor 负载因子,默认值0.75f
默认初始化长度 16
链表存储模型:
java8中引入
- 存储元素小于等于6时为链表
- 元素为7时不做改变
- 元素为8时变为红黑树
因为他本身所有的位置都为null,在put插入的时候会根据key的hash去计算一个index值。
数据本身是通过key值得hash值来进行存储,虽然hashmap提供的是散列存储 意思是使数据均匀的分布在不同的hash位置上 但实际场景中还是有很多key值是属于同一个hash值下的 意思就是存储在数组的同一个位置上 而在这个位置上存储的就是一个链表 在查询时 会先通过key值得hash值 找到hash值所在的链表 然后在链表上递归查询出对应的值
HashMap中存储的每个元素 都是存储在一个Node<K, V>对象中的 在这个对象中会存储 key的hash值 key值 value值及下一个元素 所以hashmap中的链表是一个单向链表
2、 链表插入方法
java8之前是头插法,就是说新来的值会取代原有的值,原有的值就顺推到链表中去,就像上面的例子一样,因为写这个代码的作者认为后来的值被查找的可能性更大一点,提升查找的效率。
但是,在java8之后,都是所用尾部插入了。
但是为什么会有存头存尾的改变呢?
首先要看一下HashMap如何扩容
扩容发生的前提由两个参数影响 一个是capacity容量,LoadFactor负载因子,默认值0.75f
意思就是 如果一个容量为100的map,当你存入第76个的时候就需要扩容了 也就是resize
扩容主要分为两步
- 扩容:创建一个新的Entry空数组,长度是原数组的2倍
- ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组
需要rehash的原因
因为hash的公式是 HashCode(Key) & (Length - 1)
所以长度扩大之后,hash的规则也就随之改变
其次需要知道的是jdk1.7时的头插方法有什么弊端
举例:
我们要在容量为2的容器里面用不同线程插入A,B,C,假如我们在resize之前打个短点,那意味着数据都插入了但是还没resize那扩容前可能是这样的。
我们可以看到链表的指向A->B->C
而扩容resize之后
因为resize的赋值方式,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置,在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。
如果此时AB的hash值还是相同 那么就会出现一种情况 因为赋值方式是头插 所以这时先存的是B元素 然后放A 这时B指向了A A指向的是B 一旦开始查找就显示了死循环
Java7在多线程操作HashMap时可能引起死循环,原因是扩容转移后前后链表顺序倒置,在转移过程中修改了原来链表中节点的引用关系。
而使用头插会改变链表的顺序,但是如果使用尾插在扩容是就会保持链表元素原本的顺序,就不会出现链表成环的问题了
java8之后链表有红黑树的部分,大家可以看到代码已经多了很多if else的逻辑判断了,红黑树的引入巧妙的将原本O(n)的时间复杂度降低到了O(logn)。(红黑树知识待学习!!!)
虽然多线程中 java1.8扩容后依然能保持数据顺序的一致 但是并不能保证在多线程中直接使用hashmap 因为put/get方法并没有加同步锁 你就无法保证 上一秒put的和下一秒get的是可能还是原值 所以线程安全并不保证
3、 初始化大小为什么是16呢?
直接粘代码
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
首先为啥写成1<<4呢 ?
因为java实际上还是基于2进制算法来编译和运行的 所以这样写实际能帮助 jvm编译时更加快速的进行编译
上面说了 既然是2进制的算法 那么在扩容时当时也是2的幂数 更方便内存的读写和运算了
但是为啥是16嘞? 感觉还是编码大人的习惯吧 或者觉得这个数在日常的编码过程中 更平均一些 能满足大多数场景的基本map使用
学习自
https://juejin.im/post/5dee6f54f265da33ba5a79c8 敖丙大神