对比JDK8与JDK7中HashMap的差异

不管在开发还是在面试过程中我们经常会接触到HashMap,本文就主要对HashMap存取过程以及数据结构实现原理从源码层面上进行理解。

一. HashMap的实现原理

  1. HashMap基于hashing原理,通过put(key,value)和get(key)方法存储和获取对象。
  2. 当存储对象时,将键值对传递给put(key,value)方法时,它将调用键对象key的hashCode()方法来计算hashCode,然后找到bucket(桶)位置,来存储对象value。
  3. 当获取对象时,也是先计算key的hashCode,然后找到数组中对应位置的bucket位置,然后通过key的equals()方法找到正确的键值对key-value,然后返回值对象value
  4. HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会存储在链表的下一个结点中。Hash每个链表结点中,存储键值对key-value对象。也就是当两个不同的键对象key的hashCode相同时,它们会存储在同一个bucket位置的链表(JDK8链表长度大于8变红黑树)中,取数据可通过键对象key的equals()方法用来找到正确的键值对key-value。

二.HashMap底层数据结构

在这里插入图片描述

上图为JDK8以前HashMap的底层数据结构,是通过数组+链表实现的,它之所以有相当快的查询速度主要是因为它是通过key计算hashCode来决定一维数组中存储的位置,而增删靠的是链表保证。

在这里插入图片描述

而在JDK1.8中 HashMap用数组+链表+红黑树的结构来优化,链表长度大于8同时并且满足HashMap中元素个数大于64则变成红黑树,长度小于6变回链表。

三. 什么是哈希表

上面介绍了一下HashMap的数据结构,其实他们都是基于哈希表来实现的。

散列表(Hash Table,也叫哈希表),是根据关键码值(Key Value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数也叫作散列函数,存放记录的数组叫做散列表。hash表里可以存储元素的位置称为桶(bucket)。HashMap就是基于哈希表来存储数据的。

四.哈希冲突解决方案

通常情况下用哈希表存储数据就要考虑哈希冲突怎么解决,那么哈希冲突又是什么意思呢?

当我们通过key计算hashcode时,有可能会出现不同的key通过哈希函数得到的hashcode相同,因此存储数据时定位到的bucket位置发生了冲突,这种情况我们称为哈希冲突

解决哈希冲突的方法通常有以下几种:

  1. 开放地址法。

    探测序列,当计算的hashcode冲突时就像后面的bucket位置遍历,查找一个空的bucket存放数据。探测方法又包括线性探测、再平方、伪随机

  2. 链地址法

    对于相同的值,使用链表进行连接。使用数组存储每一个链表。HashMap中使用此方案来解决的哈希冲突。

  3. 公共溢出区法

    建立一个特殊存储空间,专门存放冲突的数据。此方法适用于数据和冲突较少的情况。

  4. 再散列法

    准备若干个hash函数,如果使用第一个hash函数发生了冲突,就使用第二个hash函数,依次类推。

五. HashMap源码核心成员变量

在阅读HashMap源码之前我们要知道hashMap中的几个核心成员变量所代表的意思以及作用。

JDK7中HashMap核心成员变量

  1. Entry[] table。这个是Entry类型的数组存储了HashMap的真正数据。
  2. size大小。代表HashMap内存储了多少个键值对。
  3. capacity容量。实际上HashMap没有一个成员叫capacity,它是作为table这个数组的大小而隐式存在
  4. threshold阈值和loadFactory装载因子。threshold是通过capacity*loadFactory得到的。当size超过threshold时(刚好相等时不会扩容),HashMap扩容会再次计算每个元素的哈希位置。
  5. entrySet、keySet和values这三个都是一种视图,真正的数据都来自table。

JDK8中HashMap核心成员变量

  1. Node[] table。这个Node类型的数组存储了HashMap的真正数据。static class Node<K,V> implements Map.Entry<K,V>。
  2. size大小。代表HashMap内存储了多少个键值对。
  3. capacity容量。实际上HashMap没有一个成员叫capacity,它是作为table这个数组的大小而隐式存在(默认初始化容量为16)
  4. threshold阈值和loadFactory装载因子(默认加载因子是0.75)。threshold是通过capacity*loadFactory得到的。当size超过threshold时(刚好相等时不会扩容),HashMap会扩容 哈希计算比之前有改进。
  5. entrySet、keySet和values这三个都是一种视图,真正的数据都来自table。
  6. TREEIFY_THRESHOLD 转换成红黑树的临界值,默认8
  7. UNTREEIFY_THRESHOLD 红黑树转换成链表的临界值,默认6
  8. MIN_TREEIFY_CAPACITY 最小树化阈值 默认64

六.HashMap源码分析

1. JDK7中的HashMap

1) put(key,value)操作

在这里插入图片描述

在put(key,value)方法中我们可以看到如果table是一个空table,就会对表进行初始化操作inflateTable(threshold);

如果key==null就会执行putForNullKey(value)遍历table[0]是否存在key再决定value是放入还是覆盖;

key!=null就会计算hash值找到bucket的位置,然后遍历如果找到响应的key就覆盖value,没找到就添加数据。

在添加数据前会对key进行hash()运算:

在这里插入图片描述

从图中我们可以看到jdk7中hashMap的哈希是进行了9次扰动,分别是5次异或操作,4次位移操作,比较复杂。

计算完hashcode还要判断是否需要扩容:

在这里插入图片描述

如果size>=threshold(capacity*loadFactory也就是大于容量的0.75倍)时会进行扩容,扩容到原来的两倍,会将数据元素重新进行计算位置,然后放入newTable中,hash位置会重新计算。

最后真正插入数据:

在这里插入图片描述

在这里插入图片描述

插入Entry的时候采用的是头插法。

最后归纳一下整个put操作的流程图:

在这里插入图片描述

2) get(key)操作

在这里插入图片描述

get操作首先判断key是否为null,为null就执行getForNullKey()从table[0]中去查找;

key不为null的时候就执行getEntry(key);

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值