数据结构——HashMap

  • HahsMap内部是基于哈希表实现的键值对存储,每一个键值对也叫做Entry(入口)
  • 这些键值对(Entry)分散在一个数组中,这个数组就是HashMap的主干
  • HashMap基于hashing原理,我们通过put()get()方法存储和获取对象
  • HashMap使用LinkedList来解决碰撞问题,当发生碰撞了,对象会存储在linkedList的下一个节点中
  • 当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的LinkedList中,键对象的equals()方法用来找到键值对。

什么是哈希表

在讨论哈希表之前,我们先大概了解下其他数据结构在新增,查找等基础操作执行性能

数组:采用一段连续的存储单元来存储数据。对于指定下标的查找,时间复杂度为O(1);通过给定值进行查找,需要变量数组,逐一比对给定关键字和数组元素,时间复杂度为O(n),当然,对于有序数组,则采用二分查找,插值查找,斐波那契查找等方式,可以将查找复杂度提高为O(logn);对于一般的插入删除操作,涉及到数组元素的移动,其平均复杂度也为O(n)

链表:是一种物理存储单元上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链表次序实现的。对于链表的新增,删除等操作(在找到指定位置后),仅需处理结点时间引用即可,时间复杂度为O(1),而查找操作需要遍历链表逐一进行比对,复杂度为O(n)

二叉树:对一颗相对平衡的有序二叉树,对其进行插入,查找,删除等操作,平均复杂度为O(logn).

哈希表:相对上述几种数据结构,在哈希表中进行添加,删除,查找等操作,性能十分之高,不考虑哈希冲突的情况下,仅需要一次定位即可完成,时间复杂度为O(1)。

我们知道,数据结构的物理存储结构只有两种:顺序存储结构链式存储结构(像栈,队列,树,图等是从逻辑结构去抽象的,映射到内存中)。

而在上面我们提到过,在数组中根据下标查找某个元素,一次定位就可以达到,哈希表利用了这种特性,哈希表的主干就是数组

哈希冲突

然而万事无完美,如果两个不同的元素,通过哈希函数得出的实际存储地址相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?

a.链地址法:将哈希表的每一个单元作为链表的头结点,所有哈希地址为i的元素构成一个同义词链表。即发生冲突时就要把该关键字链在以该单元为头结点的链表的尾部
b.开放地址法:即发生冲突时,去寻找下一个空的哈希地址,只要哈希表足够大,总能找到空的哈希地址
c.再哈希法:即发生冲突时,由其他的函数再计算一次哈希值
d.建立公共溢出区:将哈希表分为基本表和溢出表,发生冲突时,将冲突的元素放入溢出表

HashMap实现原理

HahsMap数组每一个元素初始值都是Null
在这里插入图片描述
对于HashMap,我们最常用的方法:Get和Put

Put方法的原理:

调用 Put 方法的时候发生了什么呢?

比如调用 hashMap.put(“apple”, 0) ,插入一个Key为“apple”的元素。这时候我们需要利用一个哈希函数来确定Entry的插入位置(index):

index = Hash(“apple”)

假定最后计算出的index是2,那么结果如下:
在这里插入图片描述
但是,因为HashMap的长度是有限的,当插入的Entry越来越多时 ,再完美的Hahs函数也难免会出现index冲突的情况

比如下面这样:
在这里插入图片描述
这时候该怎么办呢?我们可以利用链表来解决。

每一个Entry对象通过Next指针向它的下一个Entry节点。当新来的Entry映射到冲突的数组位置时,只需要插入到对应的链表即可:

在这里插入图片描述
需要注意的是,新来的Entry节点插入链表时,使用的是“头插入法”。

Get方法的原理

使用Get方法根据Key来查找Value的时候,发生了什么呢?
首先会把输入的key做一次Hash映射,得到对应的index;
index =Hash(“apple”)

由于刚才所说的hash冲突,同一个位置有可能匹配到多个Entry,这时候就需要顺着对应链表的头节点,一个一个向下来查找。假设我们要查找的key是“appel”:
在这里插入图片描述

第一步,我们查看的头节点Entry6,Entry6的key是banana,显然不是我们要找的结果
第二步,我们查看的是Next节点Entry1,Entry1的key是appel,正是我们要找的结果

之所以把Entry6放在头节点,是因为HashMap的发明者认为,后插入的Entry被查找的可能性更大

HashMap的默认长度是16,自动扩展或初始化时,长度必须是2的幂

目的:服务于key映射到index的hashsaunfa
之前说过,从key映射到HashMap数组的对应位置,会用到一个Hash函数

index = Hash(“apple”)

如何实现一个尽量均匀分布的Hash函数呢?我们通过利用Key的HashCode值来做某种运算。

Hash算法的实现采用了位运算的方式

如何进行位运算呢?有如下的公式(Length是HashMap的长度):

index = HashCode(Key) & (Length -1

下面我们以值为“book”的 Key 来演示整个过程:

  1. 计算 book 的 hashcode,结果为十进制的 3029737,二进制的101110001110101110 1001。
  2. 假定 HashMap 长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111。
  3. 把以上两个结果做与运算,101110001110101110 1001 & 1111 = 1001,十进制是9,所以 index=9。(与运算:和,同位上都为1则为1,否则为0

可以说,Hash 算法最终得到的 index 结果,完全取决于 Key 的 Hashcode 值的最后几位。

未完待续~

原文地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值