彻底理解HashMap
今天看了许多hashmap的博客,在之前的基础上有了更深入的了解,有了一些自己的思考,在这里做一下总结。
为什么要引入hashmap-->存储机制-->解决hash冲突-->扩容
实际上就是老套路,问题引入->解决问题->产生新问题->解决新问题,于此同时在解决新问题的同时不忘初心(在这里可以说一下我对程序的一点感悟,程序/编程 实际上就是save time 和 kill time,解决问题的初心就是save time),方得始终。
为什么要引入hasmap?
如何有效的存储key-value键值对?
实际上key-value是一种常用的数据结构,本质上就是定义一个类,它只有俩个属性,一个key表示键,另一个value表示值,这样这个类就代表了一个键值对key-value。但是实际上为了符合设计原则,也就是为了面向接口编程,就是我们常用的Map接口,具体就是Map.Entry(Map的一个内部接口),也就是说,我们的具体集合类实现了Map.Entry接口
自定义集合类->设计原则(面向接口编程)->抽象出Map.Entry接口,使自定义集合类实现接口
如何存储集合?
在java中一切皆是对象,我们前面将的集合实际上就是类对象,存储一个对象的集合不外乎俩种方式:数组和链表
在这里简单交代一下数组和链表的优缺点:
数组: 1.数组一旦定义,则大小固定。2.数组插入和删除的效率低,时间复杂度为O(n)。3.下标访问,速度快,时间复杂度为O(1)。
链表: 1.遍历效率低,也就是查询效率低,最坏为O(n) 2.资源允许的话,规模可以不断增大或者减小 2.删除和添加效率高,时间复杂度为O(1)
数组存储在内存的栈,链表存储在堆。
因为在实际中,虽然键值对的增删改查都会使用到,但是查询更多一些,所以我们选择数组
如何有效的根据key值去查找value?
建立key -> index的映射关系,实际上我们要查找key时,根据映射关系来查找数组的下标,然后得到对应的key-value对象,时间复杂度可以控制再O(1);key映射成index的方法称为hash算法。
hash冲突的解决和新问题的引入(链表长度过长)
实际上解决hash冲突采用的就是拉链法,也就是链地址法,本质就是在产生冲突的位置改为单链表存储。
我们可以思考一下,我们最初选择数组存储集合的目的是为了查询效率高,现在为了解决hash冲突而在冲突位置采用了单链表存储,也就是说当单链表过长时查询效率就会很低(时间复杂度o(n)),违背了我们的初衷。
当然,java的设计者也解决了这一问题,在java8中当链表的长度超过8之后,将会自动将链表转换成红黑树,可以实现o(logn)的时间复杂度,以此来提高查询的性能(实际上除了添加,其它性能都提升了)。
数组的扩容
在数组容量不足时,为了继续维持利用数组索引查找的优良性能,我们必须对数组进行扩容操作,在HashMap的实现中,提供了一个加载因子,默认是0.75,当元素到达现有hash表的%75的时候,就会进行扩容,每次扩容都会将新数组的大小设为原数组大小的俩倍,扩容时会对链表中的元素进行重排序(重新进行运算)来找到新的位置,从而降低发生碰撞的概率。