【JAVA基础】HashMap知识点整理

一、HashMap数据结构

JDK1.8之前:数组+链表,如下图所示:

HashMap 数据结构为 数组+链表,其中:链表的节点存储的是一个 Entry 对象,每个Entry 对象存储四个属性(hash,key,value,next)

by zhanghaolin

三句话,说清它的数据结构:

整体是一个数组;
数组每个位置是一个链表;
链表每个节点中的Value即我们存储的Object;

JDK1.8:数组+链表+红黑树,如下图所示:

二、实现原理

首先有一个每个元素都是链表的数组,当添加一个元素(key-value)时,就首先计算元素key的hash值,以此确定插入数组中的位置,但是可能存在同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,但是形成了链表,同一各链表上的Hash值是相同的,所以说数组存放的是链表。而当链表长度太长(大于8)时,链表就转换为红黑树,这样大大提高了查找的效率。

三、工作原理

首先,初始化 HashMap,提供了有参构造和无参构造,无参构造中,容器默认的数组大小 initialCapacity 为 16,加载因子loadFactor 为0.75。容器的阈(yu)值为 initialCapacity * loadFactor,默认情况下阈值为 16 * 0.75 = 12; 后面会讲到阈值有啥用。

然后,这里我们拿 PUT 方法来做研究:

第一步:通过 HashMap 自己提供的hash 算法算出当前 key 的hash 值

第二步:通过计算出的hash 值去调用 indexFor 方法计算当前对象应该存储在数组的几号位置

第三步:判断size 是否已经达到了当前阈值,如果没有,继续;如果已经达到阈值,则先进性数组扩容,将数组长度扩容为原来的2倍。

> 请注意:size 是当前容器中已有 Entry 的数量,不是数组长度。

第四步:将当前对应的 hash,key,value封装成一个 Entry,去数组中查找当前位置有没有元素,如果没有,放在这个位置上;如果此位置上已经存在链表,那么遍历链表,如果链表上某个节点的 key 与当前key 进行 equals 比较后结果为 true,则把原来节点上的value 返回,将当前新的 value替换掉原来的value,如果遍历完链表,没有找到key 与当前 key equals为 true的,就把刚才封装的新的 Entry中next 指向当前链表的始节点,也就是说当前节点现在在链表的第一个位置,简单来说即,先来的往后退。
3.1扩容机制

HashMap 使用 “懒扩容” ,只会在 PUT 的时候才进行判断,然后进行扩容。

1)将数组长度扩容为原来的2 倍
2)将原来数组中的元素进行重新放到新数组中
需要注意的是,每次扩容之后,都要重新计算原来的 Entry 在新数组中的位置,为什么数组扩容了,Entry 在数组中的位置发生变化了呢?所以我们会想到计算位置的 indexFor 方法,为什么呢,我摘出了该方法的源码如下:

 static int indexFor(int h, int length) { // h 为key 的 hash值;length 是数组长度
        return h & (length-1);  
 }

由源码得知,元素所在位置是和数组长度是有关系的,既然扩容后数组长度发生了变化,那么元素位置肯定是要发生变化了。

3.2HashMap和HashTable的异同

1)二者的存储结构和解决冲突的方法都是相同的。
2)HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
3)HashTable 中 key和 value都不允许为 null,而HashMap中key和value都允许为 null(key只能有一个为null,而value则可以有多个为 null)。但是如果在 Hashtable中有类似 put( null, null)的操作,编译同样可以通过,因为 key和 value都是Object类型,但运行时会抛出 NullPointerException异常。
4)Hashtable扩容时,将容量变为原来的2倍+1,而HashMap扩容时,将容量变为原来的2倍。
5)HashMap是非线程安全的,HashTable是线程安全的。因此,HashMap效率比HashTable的要高。

6)Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。

7)hashtable 是遗留类,有很多没优化和冗余,故,hashtable 少用。

3.3优化HashMap

初始化 HashMap 的时候,我们可以自定义数组容量加载因子的大小。

3.4当两个对象的hashcode相同会发生什么?

因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用LinkedList存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在LinkedList中。

3.5如果两个键的hashcode相同,你如何获取值对象?

当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到LinkedList中正确的节点,最终找到要找的值对象。

3.6如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

容器默认的数组大小是16,默认的负载因子是0.745,当hashmap大小大于16*0.75=12时,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

1)将数组长度扩容为原来的2 倍
2)将原来数组中的元素进行重新放到新数组中

3.7为什么长度为8时才转换为红黑树?

HashMap在JDK1.8及以后的版本中引入了红黑树结构,若桶中链表元素个数大于等于8时,链表转换成树结构;若桶中链表元素个数小于等于6时,树结构还原成链表。因为红黑树的平均查找长度是log(n),长度为8的时候,平均查找长度为3,如果继续使用链表,平均查找长度为8/2=4,这才有转换为树的必要。链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。

还有选择6和8,中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。

四、红黑树

简单概括:红黑树是一种平衡的二叉查找树,因为二叉查找树不平衡时(比如左腿非常长),几乎相当于线性查找。

红黑树特性:

当插入和删除节点对红黑树的平衡造成破坏或者破坏红黑树的规则时,要进行变色、左旋转和右旋转(根据情况可交叉进行);

变色和旋转参考:

https://segmentfault.com/a/1190000014037447

http://www.360doc.com/content/18/0904/19/25944647_783893127.shtml

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值