HashMap的理解
HashMap顾名思义就是通过Hash算法存的map结构的数据。虽然不正式,但是便于理解。
-
map这就是个数据结构用来存储key value格式的
如果写一个map的简单实现应该就是:写一个数组。数组里的元素是一个key,value的对象
是的,HashMap的本质就是这个 -
那hash是什么呢?
是写HashMap的作者用到了Hash算法
什么时候用到的Hash算法呢?
就是hashmap的作者实现的时候考虑性能,然后,当你put的时候把你put(key,val)的key和默认的这个数组的长度
做了一下取模运算。算出来的模就是数组的下表
例如:put(‘key1’,‘val1’)
key1 % (数组的长度-1)= 3 所以这个array[3] = {key1, val1}了
这样做的好处是,当get(key1)的时候算一下这个key的hash就知道在什么位置了,不用再遍历一遍数组了
以上就是hashmap的极简解释。
理解的过程中需要思考以下几个问题
HashMap的默认的这个数组是多长?
数据多的时候这个数组肯定会变长吧?
如果多个数据算出来的hash一样怎么办?
比如两个key的hash算出来的都是2,那数组2这个位置存哪个?怎么存的?
以下针对jdk1.8来解决这些问题
- HashMap存的数组多长呢?
默认是16 源码里写的
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
为什么是16呢,任意一个值不行吗?
数组的长度是有用的?hash的时候长度用到了啊。所以长度是2的幂次方就可以.
- 存的数据特别多的时候数组会变长吗?
会变长的,总是保持16的话,hash重复的会很多的。
数组怎么变长的?
有一种判断机制就是超过0.75 * 数组的长度之后,这个长度就会翻倍。
比如现在,数组是16,我存了12个数了,下一个数再存的时候,数组就变成了32。
附一点源码:
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
所以啊。这是重点。对写代码很有用。如果我知道我要put多少个元素。我会算一下,自己定义一下长度。
不然的话可能会自动翻倍啊
你就会说了“扩就扩吧,他算出来的跟我自定义的不是一样的长度值吗?”
是这样虽然大小不变。但是会进行rehash
当时16的时候根据16hash过一次,当变成32了,之前的数据又会重新算一次。这个比较耗性能
所以写代码尽量避免自动扩容
- 两个hash值一样怎么办?是覆盖吗
一样的不会覆盖。会以node的形式变成链表
node是个对象里边有四个属性:
hash
key
value
next 指向下一个
会在之前的node后边插入一个node 是后插法,
jdk1.7是前插。注意理解下变成这样肯定是有用好处的.
听说过1.7时候的环形链表吗?
前插法rehash的时候,可能会形成环形链表,当你get一个key时,这个key不存在,并且hash对上了。
一个数组的key,这个里边还存了个环形链表。肯定很酸爽,会get死循环在这
- 如果很不幸,一个数组的某个index下边存了好多的node怎么办?会查询起来很慢吧?
这种不幸发生概率很小很小。一般不会发生。
jdk1.8之后node太多了就会转成红黑树的。
。。。
什么是红黑树?
为什么把node转成红黑树?
多少个node的时候转?
- 什么是红黑树?
一种平衡二叉树的优化算法。很稳, - 为什么要把node变成红黑树?
因为链表的时间复杂度是O(n),红黑树的时间复杂度O(logn) - 多少个node的时候开始转红黑树?
8个时候。但是是有条件的。
如果我没有定义hashmap的长度这时候,我存了9个key,很不幸,这9个key的hash值一样。难道要变红黑树?
当然不是。红黑树很占空间的。作者肯定权衡不想转。一看现在hasmap的长度才16,还是扩容把。这时候就变成了
32的长度,能扩容就不转红黑树的。
什么时候就开始转红黑树,不扩容了呢?
当长度大于64的时候就不扩容了。如果node数大于8了,就转红黑树了。因为这样性能更划算
希望能帮助到你