HashMap是一种常见的数据结构对象,可以保存键值对
1. HashMap方法
1.1 添加方法
1.1.1 put方法
put方法可以单次向HashMap中添加一个键值对,添加到Map中的数据,List不一样,是没有顺序的,顺序是根据哈希算法得出
1.1.2 putAll方法
可以把一个HashMap集合对象整体加入到另一个HashMap对象中,这个过程中两个集合中重复的元素会被覆盖为新的值
1.2 删除方法
1.2.1 remove方法
可以通过remove方法单次删除一个元素,删除不存在的元素不会报错
1.3 遍历方法
1.3.1 EntrySet方法
通过此遍历方法可以得到一个Entry对象的结果集,然后使用Entry对象的getKey和getValue方法进行遍历查询
1.3.2 KeySet方法
该方法先使用keySet函数,获取到HashMap的所有key的集合对象,然后循环所有的key,通过HashMap的get方法,获取到对应的value
1.4 查询方法
1.4.1 get方法
传入key值,就可以查询到对应的value
2. HashMap底层存储实现
2.1 数据结构
在jdk1.7时期,HashMap底层是通过数组和链表来进行存储,在jdk1.8之后另外加入了红黑树
2.1.1 数组
定义:采用一块连续的存储单元来存储数据
特点:按照时间复杂度来分析,数组的查询速度为O(1),删除和插入速度O(N),所以说数组的特点就是查询快,但是插入慢
ArrayList底层就是采用数组实现的,所以ArrayList查找快,但是插入慢,所以说在日常开发中,如果从数据库中取出的数据只是需要显示,不需要对该数据进行增删改,那么建议使用ArrayList,相反的需要经常去改变数据,很少去查询的时候,就可以使用LinkedList
2.1.2 链表
定义:链表是一种物理存储单元上非连续,非顺序的存储结构
特点:插入删除数据时时间复杂度为O(1),查找遍历时时间复杂度为O(N),所以说链表的特点就是插入快,查找慢,下图为单链表
LinkedList底层就是通过双向链表来实现的,下图为LinkedList里add方法底层代码
2.1.3 红黑树
根据上文可以知道,链表的查找遍历的时间复杂度为O(N),那么当链表特别长的时候,链表的查询速度将会直线下降,那么jdk1.8之后就将链表设计为达到一个特定的阈值之后会将链表转化为红黑树,这里简单说一下红黑树的特点:
- 每一个节点只有两种颜色:红色或者黑色
- 根节点必须是黑色
- 每个叶子节点(NIL)都是黑色的空节点
- 从根节点到叶子节点不能出现两个连续的红色节点
- 从任意节点出发,到它下面子节点的路径包含的黑色节点数目都相同
由于红黑树是个自平衡二叉搜索树,因此可以将查询的时间复杂度降为O(logn)。
注:
- 红黑树的根节点不一定是索引位置的头节点。
- 转化为红黑树节点后,链表的结构还在,通过next属性维持,红黑树节点在进行操作时都会维护链表的结构
- 在红黑树上,叶子节点也可能有next节点,因为红黑树的结构和链表的结构互不影响,不会因为是叶子节点就没有next节点
2.2 算法
HashMap采用的是哈希算法(散列),把任意长度值(key)通过散列算法变换成固定长度的key(地址)通过这个地址进行访问的数据结构,通过把关键码值映射到表中的一个位置来访问记录,以提高查找的速度
那么哈希算法的底层逻辑就是Hashcode编码,按照特定的算法来进行加密,得到一个整数值也叫做哈希值,然后当查找一个数据或者字符串的时候就可以将计算出来的整数进行对比,只用看整数是否相等就可以,而不用去暴力O(N),所以Hashcode就是为了查找算法提供的一个优秀的O(1)的解决方案
2.3哈希冲突
首先要了解哈希冲突的产生原因,Hash表的底层是由数组构成的,而数组是一个连续且有限的空间,那么从本质上讲,在有限的空间内存放无限的数据元素,那么就一定会产生冲突,因此,当对于不同的key值通过哈希算法得到了同一个散列地址,即K1≠K2,但是f(K1)=f(K2),这种现象就叫做哈希冲突
2.4 哈希冲突的解决方法
目前来说主流的解决方法有三种:
- 开放寻址法
- 再散列法
- 链地址法(拉链法)
2.5 开放寻址法
当发生哈希冲突时,如果哈希表没被装满,说明哈希表还有空余位置,那么可以将数据存放到发生冲突的下一个空位置。
空位置探测方法有两种:
- 线性探测
- 二次探测
注:在采用开放寻址法处理哈希冲突时,不能随便物理删除哈希表中的数据元素,如果直接删除元素会影响其他元素的搜索
2.5.1 线性探测
从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止,如果后面没有空位置,则重新开始从头继续找。
插入操作:通过哈希函数获取待插入元素在哈希表中的位置,如果该位置没有元素则直接插入新元素,如果该位置有元素则发生哈希冲突,使用线性探测找到下一个空位置,插入新元素
优点:处理方式简单
缺点:容易产生数据堆积
2.5.2 二次探测
线性探测的缺点是产生冲突的数据元素会堆积在一起,原因就是其找下一个空位置的方式是挨着逐个去找,因此二次探测就是为了避免这个问题。二次探测寻找空位置的方法为:index+=i^2,index为key通过哈希算法得到的位置,i为二次探测次数,如果index大于容量,那么index%=容量
当表的长度为质数并且负载因子不超过0.5时,新的表项一定可以插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在装满的问题,在搜索时可以不用考虑表装满的情况,但是插入时必须保证表的负载因子不超过0.5,如果超出必须考虑扩容
优点:解决了线性探测的数据堆积问题
缺点:元素数量上升,找空位置次数上升,找到空位置效率降低,数据规模过大会浪费空间
2.6 再散列法
对于冲突的哈希值再次进行哈希处理,直到冲突不在产生
优点:不易产生数据堆积
缺点:增加了计算时间,效率会降低
2.7 链地址法(HashMap实现)
链地址法的基本思想是对于计算后相同的值,使用链表来进行链接,使用数组存储每一个链表
优点:
- 链地址法处理冲突简单,且无堆积现象,即非同义词不会发生冲突,因此平均查找长度较短
- 由于各链表上的空间是动态申请的,所以更适合于造表之前无法确定表长的情况
- 开放地址法为减少冲突,要求的负载因子较小,数据较大时会浪费很多空间
- 使用链地址法构造的散列表中,删除元素的操作易于实现
缺点:
- 指针占用较大的空间,会造成空间浪费