HashMap笔记(自用+更新中)

一.HashMap基础使用

二.HashMap底层数据结构

1.HashMap底层数据结构

HashMap底层数据结构是:数组+链表+红黑树。

数组的结构特点:

存储空间是连续的,且占用内存严重,空间复杂,时间复杂度为O(1);优点是:随即查询的效率比较高,原因是数组连续;缺点是:插入和删除的效率低,因为每次插入和删除,这个位置以后的数据都要前移或者后移。

链表的特点:

区间离散,占用内存比较宽松,空间复杂度较小,时间复杂度O(N)。优点是:插入和删除速度快,内存利用率比较高,且空间内存不固定,扩展灵活;缺点是:查询效率比较低,原因是每次查询都要从第一个数据开始边路便利。

哈希表的特点:

哈希表别称散列表,最大的特点就是快。他的结构有很多种,常用的是 :数组+链表组成。主结构长度可以动态变化的顺序表,每个顺序表的节点可以单独引出一个链表;

大概就长这样子吧!

PS:这里先简单记忆一下吧,以后单独梳理哈希表数据结构(附件:哈希表梳理链接)

HashMap的底层数据结构:

JDK1.7版本,HashMap底层数据结构是:数组+链表;

JDK1.8之后,HashMap的底层数据结构更变为数组+链表+红黑树;

使用此结构的特点就是快,不仅是查询、插入和删除,速度都很快。

三.HashMap存储过程

1.HashMap的内部类Node

Node的是HashMap的内部静态类,实现了Map.Entry接口。是HashMap的存储单位。内部属性包括:hash、key、value、Node<K,V> next 对象;中间不仅k的hash值,还有下一个节点的引用;

    /**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     */
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

2.HashMap存储过程讲解:

(1)存储一个K-V键值对,首先会先把他们封装到一个Node节点对象中;

(2)之后底层会调HashCode()方法,计算出k的哈希值;

(3)通过(e.hash&(table.length-1)) (元素哈希值和table长度减一按位与运算)  得出该哈希值得下标索引位置;

(4)如果该下标位置没有元素,直接将该元素放到下标位置;

(5)如果该已经存在元素,则用equal方法逐个比较。如果返回的都为false,则直接将元素放到链表尾部;如果如果有元素返回的结果为true,则该元素会替换掉这个位置的元素。

用例说明:

假设有一个HashMap,目前存储结构这样子(数组长度为10)

现在我们存三个键值对进去,对应的k分别是:吴彦祖、彭于晏、岳云鹏

首先计算出这三个k的Hash值:吴彦祖 Hash1、彭于晏 Hash2、岳云鹏Hash3

计算出Hash值后,就要计算下标位置了,这里HashMap使用e.hash&(table.length-1)得到下标

假设得出下标位置:  吴彦祖—>1  、彭于晏—>3、 岳云鹏—>5

如下图所示,就这样对应:

对于“彭于晏”:下标位置为3,因该位置没有元素,则直接将“彭于晏”存到该位置;

对于“岳云鹏”:下标位置为5,因为该位置已经有元素了,所以“岳云鹏”要分别和“黑旋风”、“GGbang”进行equals比较。返回的结果都为false,直接将“岳云鹏”放到GGbang后面。

对于“吴彦祖”:下标位置为1,该位置也有值,所以“吴彦祖”要分别和该链表上的元素进行equals比较;很显然已经存在的“吴彦祖”和新加进去的“吴彦祖”比较返回结果为true,则新吴彦祖直接替换换掉原来的吴彦祖。(所以现在你就是吴彦祖)

吴彦祖、彭于晏、岳云鹏三个key存入后的位置大概长这样:

大概就是这回事吧,具体更细节的东西,比如底层代码分析等以后再补充吧。

四.HashMap扩容机制

1、什么时候扩容?

JDK1.7:下面两个条件,①②全部满足才扩容

        ①当前存放元素个数达到阈值(默认大小为16,负载因子0.75,阈值12 = 16*0.75)

        ②发生hash冲突

        其实理想状态下,Hashmap在Jdk1.7版本,未发生扩容的时候,可以存满16个值,前提是别发生hash冲突。

JDK1.8:下面两个引发扩容的条件,满足其一即可

        ①当存放新值的时候,元素个数大于等于阈值;

        ②某链表长度大于8,且数组长度小于64;

        注意:现存值,再扩容!!!要添加新值(Hashmap中已经存在put(“name”,“菜鸟”), 再put(“name”,“老菜鸟”)这种不算,不会引发扩容)

        还有一个注意点:当数组长度超过64以后,即使某链表长度超过8(HashMap元素个数未超过阈值),HashMap也不会扩容,而是该链表会转为红黑树。这是一个高频面试题!!

2、怎么扩容?

JDK1.8

大概理解:

(1)HashMap创建的时候,还没有初始化table。第一次put()后初始化table(默认table长度16,负载因子0.75,阈值为12),达到扩容条件,开始扩容

(2)HashMap扩容,table默认是翻倍(创建一个新的table),由原来的16->32

(3)重新计算下标位置e.hash&(newTable.length-1) 因为newTable是oldTable二倍长度,实际上是e.hash&(oldTable.length*2-1),那么得出结论是:下标位置要么和之前一样,要么是之前下标加上旧的table长度

那么要不要加上之前的数组长度呢,实际上是只看元素Hash值二进制第五位就好了

原来数组长度是16 ,减去1为15    二进制是  0000 1111    (按位与后高四位肯定为 0000)

新的数组长度为32,减去1为31     二进制是   0001 1111   (按位与后高四位肯定可能为 0001或0000)

a:假设一个元素A的Hash值,对应的二进制是 1011  1010

b:假设一个元素B的Hash值,对应的二进制是 1000  1010

AB两个元素,和旧数组(0000 1111)按位与得出的结果都是 0000 1010 (转十进制:10),哈希值一样,所以会存放在同一个链表或红黑树中。

AB两个元素,和新数组(0001 1111)按位与得出的结果分别是 11010和1010,对应的十进制分别为26、10  ,两者之间相差16(旧数组长度)。

新下标位置和旧下标位置关系:新下标位置要么和之前一样,要么是:新下标 = 旧下标+ 旧table长度

补充:假设求  3 & 5   ;首先算出3和5的二进制值  0011 和 0101 , 按位与结果  0001 ,十进制为1  

与运算:同为1 ,则为1。不全是1,则为0;

HashMap这样完成了扩容与元素迁移,不用重新计算元素Hash值,下标位置直接通过Hash值和table按位与即可得出。

五.HashMap常见问题(面试总结)

#高频面试点待整理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值