Java中Map和Set详细介绍,哈希桶的实现

大家好呀,前一节我们接触了二叉搜索树,那么紧接着,我们要学习一种十分重要而且也是我们在初阶数据结构中接触的最后一种数据结构—Map和Set,本篇博客将会详细介绍两种数据结构,并且针对哈希表底层实现一个哈希桶,小伙伴们快来一起学起来吧~

一,Map和Set概念与场景

Map和Set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。我们以前学习过搜索方式比如二分查找和直接遍历比较适合静态查找,即在查找元素程中,我们不会在对数据进行插入和删除操作了,但是在某些需要需要在查找时 进行一些查找操作的情形下,上述两种数据数据结构便不再适合,因此我们便引出了Map和Set这种数据,他们之间的联系非常紧密,是一种适合进行动态查找的数据结构。

模型简介

在Java中,一般把搜索的数据称为关键字(Key),和关键字对应的称为值(Value),将其称之为Key-Value的键值对,所以
模型会有两种:
1. 纯 Key 模型,纯K模型每次枝存储的是一条数据。
2. Key-Value 模型,这种模型存储的是一组数据,这种模型会在数据之间建立联系,可以通过一个数据找到另一个数据
而Map中存储的就是Key-Value的键值对,Set中只存储了Key。

二,Map

Map简介

Map是一个接口类,该类没有继承自Collection,该类中存储的是<K,V>结构的键值对,并且K一定是唯一的,不能重复,Map内部还有一个名字为Entry的接口,也就是说,我们在实Map时,需要把Entry也一并实现,Jvm为我们提供了两种实现了这个接口的类,TreeMap和HashMap(我们可以直接实例化来使用)。首先为大家介绍先为大家介绍Map里的Entry。

3.2 关于Map.Entry<K, V>的说明

在idea中我们可以看到源码

Entry这个接口在Map内部,也就是说,实现Map这个接口的类也一定实现了Entry这个接口,Map.Entry<K, V> 是Map内部实现的用来存放<key, value>键值对映射关系的内部类,我们可以通过Map的entrySet()方法可以获取Map中所有的键值对,返回一个Set集合,其中每个元素都是一个Map.Entry对象。Map.Entry接口包含了访问和操作键值对的方法,如getKey()、getValue()和setValue(),如下

1,K  getKey() 返回 entry 中的 key
2,V  getValue() 返回 entry 中的 value
3,V  setValue(V value) 将键值对中的value替换为指定value

注意:Map.Entry<K,V>并没有提供设置Key的方法

具体使用方法在讲解Map常用方法的最后一个示范,我们先可以看看TreeMap是怎样实现这个接口的

我们知道TreeMap底层是一颗红黑树,上面这张图也表示TreeMap是以红黑树的方式组织数据的。

Map的常用方法及使用

1.V put(K key, V value) 设置 key 对应的 value

演示:

import java.util.Map;
import java.util.TreeMap;public class Main {public static void main(String[] args) {Map<String,Integer> treeMap=new TreeMap<>();treeMap.put("hello",2);treeMap.put("world",3);}
}

2.V get(Object key) 返回 key 对应的 value

public class Main {public static void main(String[] args) {Map<String,Integer> treeMap=new TreeMap<>();treeMap.put("hello",2);treeMap.put("world",3);int val=treeMap.get("hello");System.out.println(val);//输出2}
}

需要注意的是get()这里方法返回的是一个Integer的值,用int接收自动拆箱,如果用Integer接受则输出null。如果key不存在则抛出异常


3.V getOrDefault(Object key, V defaultValue) 返回 key 对应的 value,key 不存在,返回默认值

和get()   方法相同,只是这个方法可以设置一个默认值,如果key不存在则返回默认的val

public class Main {public static void main(String[] args) {Map<String,Integer> treeMap=new TreeMap<>();treeMap.put("hello",2);treeMap.put("world",3);int val=treeMap.get("hello");System.out.println(val);//输出2int val1=treeMap.getOrDefault("abc",100);System.out.println(val1);//"abc"不存在,输出100}
}


4.V remove(Object key) 删除 key 对应的映射关系

简单来说就是删除Key对应的一组数据,十分简单

5.boolean containsKey(Object key) 判断是否包含 key
6.boolean containsValue(Object value) 判断是否包含 value

这两个方法就是判断是否包含某个key或者val,理解起来没有难度  这里就不作演示了


7.Set<K> keySet() 返回所有 key 的不重复集合

也就是把所有Map中的Key值组织成一个Set,Set我们后面还会继续介绍,还可以用一个Set的对象来接收,里面存放的就是所有Map里的Key值的一个集合

组织起来后我们当然就可以用Set的方法来对他进行操作


8.Collection<V> values() 返回所有 value 的可重复集合

有了可以组织Key值的Set,那么自然有组织Values的方法,不过,这个方法是把所有value值组织成一个Collection对象,这个我们从他的返回值中可以清楚的反映出来,至于Collection嘛,他是一个类,这里我再放出这张图

当然我们也可以用一个对象来接收

Collection<Integer> collection=treeMap.values();

9.Set<Map.Entry<K, V>> entrySet() 返回所有的 key-value 映射关系

这个方法和我们上面所说的的Entry接口有关,它主要是把Map的一个个节点组织成一个Set,注意是节点,这个从他的返回值可以看出,下面演示用法

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;public class Main {public static void main(String[] args) {Map<String,Integer> treeMap=new TreeMap<>();treeMap.put("hello",2);treeMap.put("world",3);Set<Map.Entry<String, Integer>> set=treeMap.entrySet();//试试怎么用for—each循环遍历for (Map.Entry<String, Integer> s : set) {//注意s的类型//这里就可以调用上面entry介绍的三种方法s.getKey();s.getValue();s.setValue(1);}}
}

迭代器遍历方式

学习了entrySet方法下面额外为大家介绍一种用迭代器遍历Map的方式,

我们可以看出Set继承了Iterator接口,但是Map没有,所以我们不能直接用迭代器的方式来遍历Map,但是通过上面的方法。我们可以先把Map转化为Set,再进行遍历

import java.util.*;public class Main {public static void main(String[] args) {Map<String, Integer> treeMap = new TreeMap<>();treeMap.put("hello", 2);treeMap.put("world", 3);Set<Map.Entry<String, Integer>> set = treeMap.entrySet();Iterator<Map.Entry<String, Integer>> it = set.iterator();while (it.hasNext()) {System.out.println(it.next() + " ");}}
}

Map的方法就差不多说到这里了

注意事项:


1. Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap
2. Map中存放键值对的Key是唯一的,value是可以重复的
3. 在TreeMap中插入键值对时,key不能为空,否则就会抛NullPointerException异常,value可以为空。但是HashMap的key和value都可以为空。
4. Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。
5. Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。
6. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入。
7. TreeMap和HashMap的区别

三,Set

Set的常用方法

Set与Map主要的不同有两点:Set是继承自Collection的接口类,Set中只存储了Key。其余的很多使用方法很相似,这里简略概述

1.boolean add(E e) 添加元素,但重复元素不会被添加成功
2.void clear() 清空集合
3.boolean contains(Object o) 判断 o 是否在集合中
4.boolean remove(Object o) 删除集合中的 o
5.int size() 返回set中元素的个数
6.boolean isEmpty() 检测set是否为空,空返回true,否则返回false

上面六个方法很基础也很简单,这里一起演示

public class Main {public static void main(String[] args) {Set<String> treeSet=new TreeSet<>();treeSet.add("hello");treeSet.add("world");boolean boo=treeSet.contains("hello");//输出true,不存在则输出falseint size=treeSet.size();//2treeSet.remove("hello");System.out.println(treeSet.isEmpty());treeSet.contains("hello");//false}
}

7.Iterator<E> iterator() 返回迭代器

和上面介绍过的相同,主要是迭代器遍历

public class Main {public static void main(String[] args) {Set<String> treeSet=new TreeSet<>();treeSet.add("hello");treeSet.add("world");Iterator<String> iterator=treeSet.iterator();while (iterator.hasNext()){System.out.println(iterator.next()+" ");}}
}


8,Object[] toArray() 将set中的元素转换为数组返回

需要注意返回的是Object对象需要强转

public class Main {public static void main(String[] args) {Set<String> treeSet=new TreeSet<>();treeSet.add("hello");treeSet.add("world");String[] strings= (String[]) treeSet.toArray();}
}


9.boolean containsAll(Collection<?> c) 集合c中的元素是否在set中全部存在,是返回true,否则返回false

public class Main {public static void main(String[] args) {Set<String> treeSet=new TreeSet<>();treeSet.add("hello");treeSet.add("world");Set<String> treeSet1=new TreeSet<>();treeSet.add("hello");treeSet.add("abc");treeSet.contains("hello");treeSet.containsAll(treeSet1);}
}


10.boolean addAll(Collection<? extendsE> c) 将集合c中的元素添加到set中,可以达到去重的效果

注意,这个集合必须继承自Collection

import java.util.*;public class Main {public static void main(String[] args) {Set<String> treeSet=new TreeSet<>();treeSet.add("hello");treeSet.add("world");Set<String> treeSet1=new TreeSet<>();treeSet.add("hello");treeSet.add("abc");treeSet.addAll(treeSet1);//有去重效果for (String s:treeSet) {System.out.println(s);}}
}

注意事项

1. Set是继承自Collection的一个接口类
2. Set中只存储了key,并且要求key一定要唯一
3. TreeSet的底层是使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入到Map中的

(源码)


4. Set最大的功能就是对集合中的元素进行去重
5. 实现Set接口的常用类有TreeSet和HashSet,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础上维护了一个双向链表来记录元素的插入次序。
6. Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入
7. TreeSet中不能插入null的key,HashSet可以

四,哈希桶的实现

实现哈希表有多种方式,要点还是在于如何解决哈希冲突,这里采用哈希桶的方式,用数组-链表结合来实现

哈希冲突

定义:(百度的)

哈希冲突‌是指在哈希表中,两个或更多个不同的键被映射到了同一个哈希桶的情况这种情况可能会导致数据丢失或者检索效率下降,因为不同的键被映射到了同一个位置,需要额外的操作来处理这种冲突。

简单来说,我们再用哈希表(注:哈希表可以是数组,链表,树等数据结构)存储数据时,因为要根据要插入的数值大小来确定这个树的存储位置,就有可能会出现多个数据映射到同一位置的情况,举个例子:

我们定义一个长度为10的哈希表(这里采用数组)并以数据在数组中的下标=数据大小%数组长度的方式来确定数组位置,那么这时候,我们可能会遇到一个问题,如图

当然,为了解决这个问题也有很多方法,我们这里采用哈希桶的方法,即采用数组+链表的方式来存储数据

这样,哈希冲突便得到了有效解决,当然,如果数据过多的时候,我们就会对数组进行扩容,一般来说,当数据个数/数组长度 >0.75时,我们对数组进行扩容。这里的0.75我们称作负载因子,可以自己定义,一般在0.75左右最佳

代码实现

初始准备:

因为我们采用数组+链表模拟,所以我们需要定义链表节点的值和存放这个链表的数组

class HashBuck{class Node{int key;//哈希表是KEY-VAl模型int val;Node next;Node(int val,int key){this.val = val;this.key = key;}}Node[] arrays=new Node[10];//初始数组长度int useSize=0;//数组里面元素个数}
}

插入操作

假设我们在传入值为key和关键码为val的值,我们一般以key%数组长度来确定此值在数组中的位置,在插入时,还需要判断数组中是否有值为val的值,因为哈希表中的值不能重复,所以如果有值为val的值时,我们便更新它的关键码为新的key值,否则,我们在数组对应位置中的链表中头插或者尾插一个新的节点,这里选择头插或者尾插均可,不是我们关注的重点,最后别忘记了useSize++

public void Insert(int val,int key){int index=key/ arrays.length;//确定数据在数组中的位置Node cur=arrays[index];//用于遍历数组中的链表while(cur!=null){if(cur.val==val){//有值为val的节点则更新它的key后走人~cur.key=key;return;}cur=cur.next;}Node node=new Node(val, key);//这里选择头插的方式node.next=arrays[index];arrays[index]=node;useSize++;
}

在插入操作基本完成后,这个时候就需要我们关注哈希冲突的问题,当负载因子大于0.75时,我们就对数组进行扩容,写一个计算负载因子的方法,如果超过负载因子,就对数组进行扩容。但这个时候千万不能像顺序表那样扩容,因为数组的变化会引起代码中的index的变化(index=key%数组长度),这个时候我们需要进行遍历和重新哈希,来达到调整的目的,此时我们可以重新封装一个方法

计算负载因子

private static final double LoadFactor=0.75f;
private double doLoadFactor(){return useSize*1.0/arrays.length;//useSize*1.0才能计算出小数}

然后就可以来调整数组大小了,调整过后需要重新哈希

重新哈希

 private void resize() {Node[] array=new Node[2*arrays.length];//新的数组for (int i = 0; i <array.length; i++) {//开始遍历Node cur=arrays[i];while(cur!=null){Node curN=cur.next;//必须要先记录下下一个位置,否则可能会因为下面的操作打乱int index=cur.key/array.length;//新的indexcur.next=array[index];array[index]=cur;cur=curN;}}arrays=array;//新的数组给旧的数组}

最后提一句,在JVM中,当数组长度超过64或者链表长度超过8时,就会把哈希桶调整成树

完整代码

class HashBuck{class Node{int val;int key;Node next;Node(int val,int key){this.val = val;this.key = key;}}Node[] arrays=new Node[10];int useSize=0;private static final double LoadFactor=0.75f;
//插入public void Insert(int val,int key){int index=key/ arrays.length;Node cur=arrays[index];while(cur!=null){if(cur.val==val){cur.key=key;return;}cur=cur.next;}Node node=new Node(val, key);node.next=arrays[index];arrays[index]=node;useSize++;if(doLoadFactor()>0.75){resize();}}
//重新哈希private void resize() {Node[] array=new Node[2*arrays.length];for (int i = 0; i <array.length; i++) {Node cur=arrays[i];while(cur!=null){Node curN=cur.next;int index=cur.key/array.length;cur.next=array[index];array[index]=cur;cur=curN;}}arrays=array;}
//计算负载因子private double doLoadFactor(){return useSize*1.0/arrays.length;}
}

以上就是全部内容了,感谢大家支持。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/437864.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从0到1深入浅出构建Nest.Js项目

Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用JavaScript 的渐进增强的能力&#xff0c;使用并完全支持 TypeScript &#xff08;仍然允许开发者使用纯 JavaScript 进行开发&#xff09;&#xff0c;并结合了 OOP &#xff08;面向对…

Java的学习(语法相关)

字符串存储的问题 char 和字符串都是字符的集合&#xff0c;它们之间的确有相似性&#xff0c;但在 Java 中它们有着不同的存储机制和处理方式。让我从 char 和 String 的本质区别入手来解释。 1. char 和 String 的区别 char 是基本类型&#xff1a;char 是 Java 中的基本数据…

Java-数据结构-Map和Set(三)-习题 o(´^`)o

目录 ❄️一、习题一(只出现一次的数字)&#xff1a; ❄️二、习题二(随机链表的复制)&#xff1a; ❄️三、习题三(宝石与石头)&#xff1a; ❄️四、习题四(旧键盘)&#xff1a; ❄️五、习题五(前k个高频单词)&#xff1a; ❄️总结&#xff1a; ❄️一、习题一(只出现一…

Python(三)——列表

文章目录 创建列表访问下标遍历列表元素新增元素查找元素删除元素连接列表切片操作 创建列表 创建列表主要有两种方式 [ ]表示一个空的列表 a [] print(type(a)) # <class list> print(a) # []通过list()的方式来创建一个空列表 a list() print(type(a)) # …

Java对象头

一、对象在堆内存中的布局 1.定义 在HotSpot虚拟机中&#xff0c;对象在堆内存的存储布局可以划分为三个部分&#xff1a;对象头&#xff08;Header&#xff09;、实例数据&#xff08;Instance Data&#xff09;、和对齐填充&#xff08;Paddin&#xff09;。 二、对象在堆内…

Rstudio:强大的R语言集成开发环境(IDE)

Rstudio 应该是 R 语言使用的标配&#xff0c;尽管 Rstudio 的母公司 Posit 推出了新一代的集成开发环境 Positron&#xff0c;但其还处于开发阶段。作为用户不妨让其成熟后再使用&#xff0c;现阶段还是 Rstudio 更稳定。 如果你在生物信息学或统计学领域工作&#xff0c;R语言…

【springboot】整合沙箱支付

目录 1. 配置沙箱应用环境2. 配置springboot项目1. 引入依赖2. 配置文件注册下载ngrok 3. 创建支付宝支付服务类4. 支付界面模板5. 控制类实现支付6. 测试 1. 配置沙箱应用环境 使用支付宝账号登录到开放平台控制台。 使用支付宝登录后&#xff0c;看到以下页面&#xff0c;下…

动态内存分配

1. 基本使用 在内存空间中&#xff0c;我们如何做到想用多少内存空间就申请多少内存空间&#xff1f; 使用以下函数可以实现&#xff1a; 如何利用malloc申请一片连续的内存空间&#xff1a; int* p malloc(100 * sizef(int)); 该代码实现了&#xff0c;申请一片空间&#…

VS开发 - 静态编译和动态编译的基础实践与混用

目录 1. 基础概念 2. 直观感受一下静态编译和动态编译的体积与依赖项目 3. VS运行时库包含哪些主要文件&#xff08;从VS2015起&#xff09; 4. 动态库和静态库混用的情况 5. 感谢清单 1. 基础概念 所谓的运行时库&#xff08;Runtime Library&#xff09;就是WINDOWS系统…

828华为云征文|WordPress部署

目录 前言 一、环境准备 二、远程连接 三、WordPress简介 四、WordPress安装 1. 基础环境安装 ​编辑 2. WordPress下载与解压 3. 创建站点 4. 数据库配置 总结 前言 WordPress 是一个非常流行的开源内容管理系统&#xff08;Content Management System, CMS&#xf…

进度条(倒计时)Linux

\r回车(回到当前行开头) \n换行 行缓冲区概念 什么现象&#xff1f; 什么现象&#xff1f;&#xff1f; 什么现象&#xff1f;&#xff1f;&#xff1f; 自己总结&#xff1a; #pragma once 防止头文件被重复包含 倒计时 在main.c中&#xff0c;windows.h是不可以用的&…

CleanMyMac X v4.12.1 中文破解版 Mac优化清理工具

在数字时代&#xff0c;我们的Mac设备承载着越来越多的重要信息和日常任务。然而&#xff0c;随着时间的推移&#xff0c;这些设备可能会变得缓慢、混乱&#xff0c;甚至充满不必要的文件。这就是CleanMyMac X发挥作用的地方。 CleanMyMac X是一款功能强大的Mac优化工具&#…

Python 从入门到实战32(数据库MySQL)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们讨论了数据库编程接口操作的相关知识。今天我们将学习…

CSP-J Day 3 模拟赛补题报告

姓名&#xff1a;王胤皓&#xff0c;校区&#xff1a;和谐校区&#xff0c;考试时间&#xff1a; 2024 2024 2024 年 10 10 10 月 3 3 3 日 9 : 00 : 00 9:00:00 9:00:00~ 12 : 30 : 00 12:30:00 12:30:00&#xff0c;学号&#xff1a; S 07738 S07738 S07738 请关注作者的…

[20241003] 狂飙500天,国产大模型如何突破商业化之困?

大模型加速狂飙&#xff0c;AI商业化却面临巨大鸿沟。 一方面&#xff0c;传统企业不知道怎么将AI融入原始业务&#xff0c;另一方面&#xff0c;AI企业难以找到合适的变现方式。AI企业究竟该如何突破商业化之困&#xff1f;B端和C端&#xff0c;呈现出两种不同的路径。 纵…

Pikachu-暴力破解-验证码绕过(on client)

访问页面&#xff0c; 从burpsuite 上看到返回的源代码&#xff1b; 验证码生成时通过 createCode 方法生成&#xff0c;在前端页面生成&#xff1b; 同时也是在前端做的校验&#xff1b; 直接验证&#xff1b;F12 -- 网络&#xff0c;随便输入个账号、密码、验证码&#xff0…

OceanBase—02(入门篇——对于单副本单节点,由1个observer扩容为3个observer集群)——之前的记录,当初有的问题未解决,目前新版未尝试

OceanBase—02&#xff08;入门篇——对于单副本单节点&#xff0c;由1个observer扩容为3个observer集群&#xff09;——之前的记录&#xff0c;有的问题未解决&#xff0c;新版未尝试 1、前言—安装单副本单节点集群1.1 docker安装OB 2、查看现有集群情况2.1 进入容器&#x…

计算机网络的整体认识---网络协议,网络传输过程

计算机网络背景 网络发展 独立模式: 计算机之间相互独立; 网络互联: 多台计算机连接在一起, 完成数据共享; 局域网LAN: 计算机数量更多了, 通过交换机和路由器连接在一起; 广域网WAN: 将远隔千里的计算机都连在一起;所谓 "局域网" 和 "广域网" 只是一个相…

【EXCEL数据处理】000011 案列 EXCEL带有三角形图标的单元格转换,和文本日期格式转换。

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【EXCEL数据处理】000011 案列 EXCEL带有三角形图标的单元格转换。使用…

基于SpringBoot+Vue+MySQL的民宿预订平台

系统展示 用户前台界面 管理员后台界面 商家后台界面 系统背景 随着旅游业的蓬勃发展&#xff0c;民宿作为一种独特的住宿方式&#xff0c;受到了越来越多游客的青睐。然而&#xff0c;传统的民宿预定方式往往存在信息不对称、效率低下等问题&#xff0c;难以满足游客的个性化需…