每日学习03--HashMap

9 篇文章 1 订阅

HashMap数据结构

一、HashMap数据结构

1、HashMap是Java中最常用的集合类框架,也是Java语言中非常典型的数据结构,

在这里插入图片描述

1、数组特点

存储区间连续,占用内存严重,空间复杂高,时间复杂为O(1)。
优点:随机读取效率很高,原因数组是连续(随机访问性强,查找速度快)。
缺点:插入和删除数据效率低,因插入数据,这个位置后面的数据在内存中要往后移的,且大小固定不易动态扩展。

2、链表特点

区间离散,占用内存宽松,空间复杂度小,时间复杂度O(N)。
优点:插入删除速度快,内存利用率高,没有大小固定,扩展灵活。
缺点:不能随机查找,每次都是从第一个开始遍历(查询效率低)。

3、哈希表特点

哈希表也叫散列表,是一种神奇的结构,最大的特点就是快。它的结构有很多种,最流行、最容易理解的是:顺序表+链表的结构。主结构是长度可以动态变化的顺序表,每个顺序表的节点可以单独引出一个链表。

二、HashMap

1、HashMap的节点:

HashMap是一个集合,键值对的集合,源码中每个节点用Node<K,V>表示

static class Node<K,V> implements Map.Entry<K,V> {
   final int hash;
   final K key;
   V value;
   Node<K,V> next;

Node是一个内部类,这里的key为键,value为值,next指向下一个元素,可以看出HashMap中的元素不是一个单纯的键值对,还包含下一个元素的引用。

2、HashMap的数据结构:

HashMap的数据结构为 数组+(链表或红黑树)
在这里插入图片描述
数组的特点:查询效率高,插入,删除效率低。
链表的特点:查询效率低,插入删除效率高。

在HashMap底层使用数组加(链表或红黑树)的结构完美的解决了数组和链表的问题,使得查询和插入,删除的效率都很高。

java1.7 之前是数组+链表 ,之后是 数组+链表+红黑树

3、HashMap存储元素的过程:

HashMap<String,String> map = new HashMap<String,String>();
map.put("刘德华","张惠妹");
map.put("张学友","大S");

第一步:计算出键“刘德华”的hashcode,该值用来定位要将这个元素存放到数组中的什么位置.

什么是hashcode?
在Object类中有一个方法:

public native int hashCode();

该方法用native修饰,所以是一个本地方法,所谓本地方法就是非java代码,这个代码通常用c或c++写成,在java中可以去调用它。
调用这个方法会生成一个int型的整数,我们叫它哈希码,哈希码和调用它的对象地址和内容有关.

哈希码的特点是:

对于同一个对象如果没有被修改(使用equals比较返回true)那么无论何时它的hashcode值都是相同的
对于两个对象如果他们的equals返回false,那么他们的hashcode值也有可能相等

明白了hashcode我们再来看元素如何通过hashcode定位到要存储在数组的哪里,通过hashcode值和数组长度取模我们可以得到元素存储的下标。

刘德华的hashcode为20977295 数组长度为 16则要存储在数组索引为 20977295%16=1的地方
在这里插入图片描述可以分两种情况:

  1. 数组索引为1的地方是空的,这种情况很简单,直接将元素放进去就好了。
  2. 已经有元素占据了索引为1的位置,这种情况下我们需要判断一下该位置的元素和当前元素是否相等,使用equals来比较。

如果使用默认的规则是比较两个对象的地址。也就是两者需要是同一个对象才相等,当然我们也可以重写equals方法来实现我们自己的比较规则最常见的是通过比较属性值来判断是否相等。

如果两者相等则直接覆盖,如果不等则在原元素下面使用链表的结构存储该元素
在这里插入图片描述
每个元素节点都有一个next属性指向下一个节点,这里由数组结构变成了数组+链表结构,红黑树又是怎么回事呢?

因为链表中元素太多的时候会影响查找效率,所以当链表的元素个数达到8的时候使用链表存储就转变成了使用红黑树存储,原因就是红黑树是平衡二叉树,在查找性能方面比链表要高.

4、HashMap中的两个重要的参数
HashMap中有两个重要的参数:初始容量大小和加载因子,初始容量大小是创建时给数组分配的容量大小,默认值为16,加载因子默认0.75f,用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍,专业术语叫做扩容.

在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能.

5、HashMap的put()和get()的实现

1、map.put(k,v)实现原理(同3)
第一步首先将k,v封装到Node对象当中(节点)。
第二步它的底层会调用K的hashCode()方法得出hash值。
第三步通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。
如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。

java1.8 中put 源码:put 中调用 putVal()方法:

1.首先判断map中是否有数据,没有就执行resize方法
2.如果要插入的键值对要存放的这个位置刚好没有元素,那么把他封装成Node对象,放在这个位置上即可
3.如果这个元素的key与要插入的一样,那么就替换一下。
4.如果当前节点是TreeNode类型的数据,执行putTreeVal方法
5.遍历这条链子上的数据,完成了操作后多做了一件事情,判断,并且可能执行treeifyBin方法

2、map.get(k)实现原理
第一步:先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
第二步:通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。
如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,
如果所有equals方法都返回false,则get方法返回null。

如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

6、java 1.7 和 java1.8 HashMap 的区别
jdk1.7中HashMap采用的是位桶+链表的方式,即我们常说的散列链表的方式,而
jdk1.8中采用的是位桶+链表/红黑树的方式,也是非线程安全的。当某个位桶的链表的长度达到某个阀值(8)的时候,这个链表就将转换成红黑树。
在jdk1.8中,如果链表长度大于8且节点数组长度大于64的时候,就把链表下所有的节点转为红黑树。

树形化还有一个要求就是数组长度必须大于等于64,否则继续采用扩容策略

总的来说,HashMap默认采用数组+单链表方式存储元素,当元素出现哈希冲突时,会存储到该位置的单链表中。但是单链表不会一直增加元素,当元素个数超过8个时,会尝试将单链表转化为红黑树存储。但是在转化前,会再判断一次当前数组的长度,只有数组长度大于64才处理。否则,进行扩容操作。

参考链接1:https://blog.csdn.net/shi_xiansheng/article/details/117792691
参考链接2:https://blog.csdn.net/java123456111/article/details/123378463

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值