最近看了数据结构与算法,并在网上看了关于Hashmap源码解析的博客,有了一些小感悟,这里对于源码就不再解释说明,想要了解的,可以去看连接中的博客,感觉写的还是不错的,虽然有些观点不太认同。
如果大家要去看的话,请带着下面几个问题来看:
1. 为什么需要HashMap这种数据结构?
2. Hashmap内部的实现是使用的什么数据结构(数组还是链表,亦或者数组加链表)?如果是链表的话,又是使用的什么数据结构来构建的?
3. 为什么Hashmap的查询速度会那么快?是怎么做到的?
4. Hashmap为什么会选择那个值来作为扩容的阀值?
5.在扩容的时候,为什么还要再计算一次,而不是简单的拷贝到新的数组里面?
6. 为什么会把null放在0的位置?
7. 为什么以2倍的原长度来进行扩容呢?
8. 为什么会有个modcount,是做什么用的?
9. 为什么在扩容的时候,会有一个最大值的限制?
10. 迭代器怎么实现?(凑个数,灵魂十问)
下面针对上面十个问题,说一下个人的理解:
1. 为什么需要HashMap这种数据结构?
对于第一个问题,是希望大家可以认真思考一下,自己的业务场景使用这种结构是不是最优的,是否可以通过其它的集合方式来替换。这里个人理解的场景是,你的业务场景需要<key,value>,或者需要依靠一个值或对象快速的找到你要的value的情景,才要去使用它,否则不要去使用它,因为它的构造需要的计算量以及复杂度比较高,不如直接使用简单的集合。
2. Hashmap内部的实现是使用的什么数据结构(数组还是链表,亦或者数组加链表)?如果是链表的话,又是使用的什么数据结构来构建的?
对于第二个问题,Hashmap中使用的是数组结合链表的形式,我们需要关注亮点,一点是数组的构建,用的是哪种类型的数组,第二点就是所使用类型的内部结构是什么样的,这里数组类型是Entry的,其内部结构包括key,value,next(用于创建单链表)以及hash(用于扩容的时候进行重计算)。
3. 为什么Hashmap的查询速度会那么快?是怎么做到的?
对于第三个问题,看过源码以后你就会知道,其查找过程,是通过计算直接得到在数组中的位置,而不是遍历的去比较。
4. Hashmap为什么会选择那个值来作为扩容的阀值?
在我们对hashmap进行查找的时候,假如链表很长的话,我们的查找效率会变低,因为在链表中找的过程是逐个遍历的,而不是通过计算直接得到的,而这个链表的建立是因为碰到了hashcode相同而key值不同的情况才建立的,也就是所谓的解决冲突而建立的,而如果数组的长度太短,元素太多,那么所产生的表的长度就会越长,查询效率就会越低,所以这里根据这种情况,利用其时间复杂度曲线,选取一个合适的加载因子计算得到的这个阀值,不过这个阀值的选定并不一定是最好的,这里考虑的是使用空间来换取时间,如果空间很大,这个加载因子可以选的更小,这样冲突的概率就更低,查询效率就会更高。
5.在扩容的时候,为什么还要再计算一次,而不是简单的拷贝到新的数组里面?
扩容的时候再计算的原因是为了把原来的链表长度变的更低,把原来冲突的情况,做进一步的减少,从而提高查找效率。
6. 为什么会把null放在0的位置?
因为我们要依赖key去做hash,而对于null进行hashcode方法的调用,势必会抛出异常(空指针异常),所以针对这种特殊的情况,我们把它放到一个特殊的位置上。个人理解,有不同理解欢迎讨论。
7. 为什么以2倍的原长度来进行扩容呢?
这里为什么选取2倍的长度进行扩容,本人也不知道为什么,可能是通过某种算法计算,认为这种扩容方式可以达到空间最少的浪费,而又可以做到相对较好的解冲突的原因吧。而本人感觉这里可以进行一下优化,我们可以根据我们的业务场景,动态的去优化这个扩容策略,比如里面写一个算法,假如碰到的情景频繁的进行扩容的话,不妨对这种业务情景一次性扩的大一些,而对于较少扩容的情况,是否可以缩减其扩容比例。
8. 为什么会有个modcount,是做什么用的?
对于modcount其使用的时候也就是在迭代器里面用一下,这里主要是防止我们获取到迭代器之后,对Hashmap又进行了操作,而没有重新获取迭代器,造成迭代器中的数据与真实的Hashmap中的数据不一致。
9. 为什么在扩容的时候,会有一个最大值的限制?
这里个人认为是为了取时间和空间的一个平衡,当不再扩容的时候,如果再增加元素冲突的概率就会增加,链表的长度也会增加,不能够再简单的通过计算来得到要查找的元素,而还需要再去遍历链表来进一步查询了。
10. 迭代器怎么实现?
这里既有数组又有链表,所以要注意一下这里的遍历顺序,首先通过循环找到不为空的next,然后遍历next的链表,如果链表到头,则继续遍历数组,找到下一个不为空的位置,这样循环的跑这个过程,直到全部遍历完。这里我们可以看到,这种离散的结构,会碰到很多的空遍历,因为有些数组位置没有元素,其效果不如ArrayList和LinkedList的迭代器。
以上是个人的一些理解,以及学习的过程,如有不同意见欢迎讨论
参考链接:
https://blog.youkuaiyun.com/u011617742/article/details/54576890
https://www.cnblogs.com/ITtangtang/p/3948406.html