您的位置:首页 > 房产 > 建筑 > Hash Table、HashMap、HashSet学习

Hash Table、HashMap、HashSet学习

2024/12/22 14:02:36 来源:https://blog.csdn.net/m0_74448051/article/details/142028789  浏览:    关键词:Hash Table、HashMap、HashSet学习

文章目录

  • 前言
  • Hash Table(散列表)
    • 基本概念
    • 散列函数
    • 散列冲突(哈希碰撞)
    • 拉链法
      • 红黑树
      • 时间复杂度分析
  • HashMap
    • 基础
    • 方法使用
      • 基本的增删改查
      • 其他的方法
    • 实现原理
  • HashSet
    • 基础操作
    • 去重原理

前言

本文用于介绍关于Hash Table、HashMap、HashSet的学习。
本文中有的地方说的是哈希,有的地方说的是散列,只需记住,在本文中哈希就是散列,散列就是哈希。

Hash Table(散列表)

散列表也就是哈希表,在散列表中使用到了红黑树和链表(下面会介绍),无论是HashMap还是HashSet,它们都是基于散列表实现的,所以先简单介绍一下散列表。

基本概念

散列表是根据键key直接访问值value的数据结构,它是由数组演化而来,利用了数组支持按下标进行随机访问数据的特性。

在数组中,索引下标就可以作为key,数组中的元素就可以作为value,我们可以通过下标索引(key)直接获取到数组中的元素(value)。

散列函数

散列表的key可以是各种各样的数据结构,而数组下标是整数,所以将各种各样数据结构的key映射为数组下标的函数就叫散列函数(哈希函数)。可以表示为hashValue=hash(key)

散列函数的基本要求

  • 散列函数计算得到的散列值必须是大于等于0的正整数,因为hashValue需要作为数组的下标
  • 如果key1==key2,那么经过散列函数计算出的hashValue一定相等,即hash(key1)==hash(key2)
  • 如果key1!=key2,那么经过散列函数计算出的hashValue一定不相等,即hash(key1)!=hash(key2)(几乎不可能)

散列冲突(哈希碰撞)

实际上想找到一个散列函数能做到不同的key计算出的hashValue值不同是几乎不可能的,这就是散列冲突,也就是指多个key的hashValue相等,映射到了同一个数组下标

拉链法

拉链法是解决哈希冲突的方法。在散列表中,数组的每个下标位置我们可以称为或者,每个桶(槽)会对应一条链表,所有散列值(hashValue)相同的元素我们会放到相同槽位对应的链表中,如下。

在这里插入图片描述

红黑树

拉链法的使用的链表我们可以改造为效率更高的红黑树,这里简单介绍一下红黑树。

红黑树:是一种自平衡的二叉搜索树(二叉搜索树:对于树中的任意一个节点,左子树节点的值都小于该节点的值,右子树节点的值都大于该节点的值),红黑树与二叉搜索树不同的就是有一个平衡机制,可以避免二叉搜索树的最差情况(链表)。

在这里插入图片描述

红黑树的特性:

  • 节点要么是红色,要么是黑色
  • 根节点是黑色
  • 叶子节点都是黑色的空节点
  • 红黑树中的红色节点的子节点是黑色的
  • 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点

在添加或删除节点的时候,如果不符合这些性质会发生旋转,以达到所有性质,完成性质的目标就是为了保证平衡

红黑树的时间复杂度

  • 查找:红黑树也是二叉搜索树,所以查找的时间复杂度为O(logn)
  • 添加:从根节点开始找到元素添加的位置,时间复杂度为O(logn),添加完成后涉及到时间复杂度为O(1)的旋转操作,所以整体时间复杂度为O(logn)
  • 删除:从根节点开始找到元素删除的位置,时间复杂度为O(logn),删除完成后涉及到时间复杂度为O(1)的旋转操作,所以整体时间复杂度为O(logn)

时间复杂度分析

  • 插入元素:只需通过散列函数计算出相应的槽位,插入槽位对应的链表的末尾即可,时间复杂度为O(1)
  • 查找和删除元素:也是先通过哈希函数计算出相应的槽位,再遍历槽位对应的链表进行插入和删除
    • 在平均情况下(元素分布比较平均)基于链表法解决哈希散列冲突的时间复杂度是O(1)
    • 散列表可能退化为链表(所有元素通过散列函数计算出的槽位都是同一个槽位),查询时间复杂度就变为了O(n)
    • 可以将链表法中的链表改为其他更高效的数据结构,如红黑树(如下图),时间复杂度为O(logn)

在这里插入图片描述

将链表法中的链表改为红黑树还有一个好处:可以防止DDos攻击(分布式拒绝服务攻击,指处于不同位置的多个攻击者对一个或多个目标进行攻击,或者一个攻击者控制位于不同位置的多个机器对目标同时实施攻击),DDos攻击可以伪装大量的key插入链表中,这样我们如果是使用的链表的话访问时效率就会非常低。

HashMap

基础

HashMap是Java中一个非常重要的数据结构,用于存储键值对(key-value)信息。它基于哈希表实现,提供了时间复杂度为O(1)的查找、插入和删除操作,所以从算法层面来说,我们常使用HashMap来判断一个元素是否在集合中。

HashMap具有以下特性:

  • 键值对存储:存储的是键值对,每个键(key)都有一个值(value)
  • 键的唯一性:在HashMap中,键是唯一的,如果先后插入两个键相同的值时,后插入的值会将前面一个值覆盖
  • 无序:不保证元素的顺序,元素的顺序可能会随着插入和删除操作而变化
  • 线程不安全:HashMap是线程不安全的,在多线程环境下,如果多个线程同时访问和修改HashMap,可能造成数据不一致的情况。

方法使用

要使用HashMap,我们需要先对其进行定义:

//键为整数类型,值为字符串类型
HashMap<Integer,String> hashMap=new HashMap<Integer,String>();

基本的增删改查

  • 增加数据
//添加键的同时添加值
hashMap.put(1,"aaa");
hashMap.put(2,"bbb");
hashMap.put(3,"ccc");
  • 删除数据
//根据键删除值
hashMap.remove(1);//清空所有数据
hashMap.clear();
  • 修改数据
//根据键修改数据
hashMap.replace(2,"bbbb");
  • 查询数据
//根据键查询数据
hashMap.get(2);

除此之外还存在一个getOrDefault()方法:

hashMap.getOrDefault(2,defaultValue);

这个方法用于查询是否存在这个key对应的value,如果没有,返回defaultValue。

String defaultValue=hashMap.getOrDefault(4,"ddd");
System.out.println(defaultValue);//由于并未添加键为4,值为ddd的数据,所以输出ddd

其他的方法

  • HashMap是否为空
hashMap.isEmpty();
  • 获取HashMap中键值对的数量
int size=hashMap.size();
  • 是否存在某个键值对
hashMap.containsKey(1);//是否存在键为1的键值对
hashMap.containsValue("aaa");//是否存在值为"aaa"的键值对
  • 分别返回所有键和值
Set<Integer> list1=hashMap.keySet();//所有键的集合
Collection<String> list2=hashMap.values();//所有值的集合

实现原理

HashMap的数据结构:底层使用散列表(哈希表)数据结构,即数组+链表或数组+红黑树

  • 当我们使用put方法向HashMap中添加元素时,会利用key的hashCode计算出hashValue(哈希值),对应两种结果

    • hashValue相同,key也相同:覆盖原始值
    • hashValue相同,key不相同:将当前的key-value存入链表或红黑树中
  • 使用get方法获取数据时,先找到hashValue对应的下标(桶或槽),再进一步通过key找到value

需要注意的是:

  • 在jdk1.8之前:拉链法只有将数组和链表结合,遇到哈希冲突就将冲突的值添加到链表中
  • 在jdk1.8之后:当链表长度大于阈值(默认为8)并且数组长度达到64时,就会将链表转为红黑树,这样做可以减少搜索时间还能防止DDos

HashSet

HashSet是一个不允许有重复元素的集合,但允许存在null值。

特性:

  • 不允许重复:不允许存储重复的元素,使用add方法存储重复的元素时会返回false
  • 无序:内部元素的存储是无序的,就算添加元素时是有序的,HashSet也不保证遍历时的顺序与添加时的一致
  • 高效:添加、删除、查找的时间复杂度都为O(1)

基础操作

  • 定义一个HashSet
Set<Integer> set=new HashSet<Integer>();
  • 添加值
set.add(100);
  • 删除元素
set.remove(100);
//移除所有元素
set.clear();
  • 判断元素是否存在
set.contains(100);

去重原理

HashSet通过hashCode()(用于计算出hashValue)和equals()(比较对象的地址值是否相同)方法实现去重。

在向HashSet添加元素时:会先调用hashCode()方法来计算出hashValue来判断对象加入的位置,如果该位置没有值,则直接插入;如果发现该位置存在值,则会调用equals()方法来判断两个对象是否相同(对象不同就是哈希冲突,上面有介绍),如果相同则添加失败返回false。

学习分享到此结束,希望能对你有所帮助!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com