【 java 集合知识 第二篇 】

目录

1.Map集合

1.1.快速遍历Map

1.2.HashMap实现原理

1.3.HashMap的扩容机制

1.4.HashMap在多线程下的问题

1.5.解决哈希冲突的方法

 1.6.HashMap的put过程

 1.7.HashMap的key使用什么类型

1.8.HashMapkey可以为null的原因

1.9.HashMap为什么不采用平衡二叉树

1.10.HashMap的负载因子

1.11.HashTable介绍

1.12.ConcurrentHashMap的原理

2.Set集合

2.1.特点

2.2.原理


1.Map集合

1.1.快速遍历Map

方式一:使用entrySet()与forEach循环

for (Map.Entry<String, Integer> entry : map.entrySet()) {System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}

方式二:使用keySet()与forEach循环

for (String key : map.keySet()) {System.out.println("Key: " + key + ", Value: " + map.get(key));
}

方式三:使用forEach与Lambda循环

map.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value)
);

方式四:使用迭代器与entrySet()或keySet()

Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {Map.Entry<String, Integer> entry = iterator.next();System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}Iterator<String> keyIterator = map.keySet().iterator();
while (keyIterator.hasNext()) {String key = keyIterator.next();System.out.println("Key: " + key + ", Value: " + map.get(key));
}

方式五:使用Stream()流Api

map.entrySet().stream().forEach(entry -> System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()));// 或者并行流(大数据量时可能提升性能)
map.entrySet().parallelStream().forEach(entry -> System.out.println("[Parallel] Key: " + entry.getKey() + ", Value: " + entry.getValue()));

1.2.HashMap实现原理

HashMap在jdk1.7及以前底层数据结构采用数组加链表的形式,当你需要添加一个键值对时,它会计算key的哈希值,再通过一定的运算(hash&(数组长度-1)其实就是hash%数组长度,不过位运算的速度快)来确定要添加到数组的哪个索引位置,确定数组索引位置后,看该数组槽位是否为空,如果为空,那么直接在该槽位中创建一个Entry对象存入要添加的键值对和key计算出的哈希值和下一个引用位置,并且将HashMap的修改次数加一,那么如果不为空,则会使用链表进行链接(头插法)在同一个哈希桶中(限制:链表过长,查询时间效率为O(n)

前提:数据结构为数组加链表(头插法)

1.添加元素,计算key的哈希值,通过运算找到数组索引位置

2.判断索引位置是否为空

3.为空,添加键值对

4.不为空,通过链表来链接在一个哈希桶中

HashMap在jdk1.8之后底层采用数组加链表或红黑树的数据结构,然后就是添加判断,数组槽位为空会在槽位中创建一个Node对象存入要添加的键值对和key计算出的哈希值和下一个引用位置(其实就是该名称了),并且将HashMap的修改次数加一,那么如果不为空,那么就会查看其数据结构,如果是链表(尾插法)添加完元素之后,)会判断该链表长度是否大于等于8,如果此时数组的长度也大于等于64,那么链表就会转换成红黑树增加查询效率和添加效率(O(n) -》 O(log n),如果数组长度没有满足,那么就会对数组进行一个扩容操作

前提:数据结构为数组加链表(尾插法)或红黑树

1.添加元素,计算key的哈希值,通过运算找到数组索引位置

2.判断索引位置是否为空

3.为空,添加键值对

4.不为空,通过链表或红黑树来链接在一个哈希桶中

5.如果使用的链表,那么判断链表的长度是否大于等于8

6.没有满足,直接结束

7.满足,判断数组长度是否大于等于64

8.满足,链表转红黑树(从链接的数据结构出发增加效率)

9.没有满足,直接将数组扩容(从数组出发,会将链接的键值对再分配)

1.3.HashMap的扩容机制

HashMap底层默认数组长度为16(2^4),当然你可以指定

它扩容的时机有两个:

  • 方式一:链表长度大于等于8,但是数组长度没有大于等于64,直接将数组长度扩容到原来的两倍
  • 方式二:此时键值对长度(实际长度)大于等于数组长度乘以负载因子(默认为0.75),直接将数组长度扩容到原来的两倍

怎么扩容的:

判断可以进行扩容时,那么会计算出新数组的长度(长度为旧数组的两倍),当然如果没有满足实际扩容要求,还是需要继续2倍,直到满足长度后,创建一个新数组,将旧数组的对象遍历(entry或node),取出哈希值,jdk1.7采用重新再哈希,当然分布更均匀,性能低,jdk1.8采用进行运算(hash&(新数组长度-1))结果大于等于旧数组长度,性能高,那么此时存入新数组的索引为旧数组长度加上原来存入旧数组索引长度,相反直接采用原先的索引长度即可,然后进行存值即可,最后改变hashMap中的数组指向引用,指向新数组

细节:为什么我们jdk1.8之后会使用的是位运算,原理其实是我们初始化的数组长度位为16(是不是就是2^4),每次扩容都是两倍,那么循环反复数组的长度都满足2的几次方,而&的规则就是全为1才是1,其余全是0,那么我们将数组长度减一,就会形成最高位为0,其余为全是1的情况,再&上哈希值,只要最终的高位为1代表需要索引需要改变

1.4.HashMap在多线程下的问题

在jdk1.7,在多线程背景下会出现Entry链死循环和数据丢失问题

在jdk1.8,解决了Entry死循环和数据丢失问题,但是在多线程背景下出现了新的问题put方法数据覆盖问题

分析:为什么jdk1.7会出现该问题,首先要了解jdk1.7链表添加一个元素采用的是头插法,好处不需要遍历添加元素,坏处自然就是以上问题,怎么导致的?

解释:此时链表:A->B->C,由于进行了扩容操作,链表中的元素需要进行添加到新的链表中(并且此时链表存入还是ABC),怎么存呢?拿出A采用头插法依次遍历,最后形成C->B->A,那如果多线程情况下对它添加操作并发执行,会不会导致出现A->B->A的问题,死循环与数据丢失

解决:jdk1.8怎么解决的呢?将头插法换成尾插法,保证了顺序

分析:为什么jdk1.8会出现put方法覆盖问题,HashMap本身是线程不安全的集合,并发执行put方法肯定会出现问题

解释:当线程1将key计算出数组索引位置,判断为空,线程2也计算另一个key它的数组索引位置与线程1一致,判断为空,那么最终数据出现了覆盖

解决:本质就是没有保证一个原子性操作,那么我们可以采用对该集合加锁或直接使用线程安全的集合

1.5.解决哈希冲突的方法

方法一:链接法

使用链表或其他的数据结构存储冲突的键值对,链接到一个哈希桶中

方法二:开放寻址法

在哈希表中找另外一个可以存储的地方进行存储键值对,常见的方法:线性探测,二次探测,双重散列

方法三:再哈希法

通过使用另外一种哈希算法,直到可以存储键值对 

方法四:哈希桶扩容

哈希冲突过多,动态的对哈希桶数据进行增加,再分配键值对,减少哈希冲突的概率 

 1.6.HashMap的put过程

1.添加元素,计算key的哈希值,通过运算找到数组索引位置

2.判断索引位置是否为空

-----

3.为空,添加键值对(创建entry或node对象)

------

4.不为空,通过链表或红黑树来链接在一个哈希桶中

5.如果使用的链表(头还是尾插法),那么判断链表的长度是否大于等于8

6.没有满足,跳转到10

--------

7.满足,判断数组长度是否大于等于64

8.满足,链表转红黑树(从链接的数据结构出发增加效率)

9.没有满足,直接将数组扩容(从数组出发,会将链接的键值对再分配)

10.看实际键值对长度是否大于等于数据长度乘以负载因子(0.75)

11.满足,对数据进行扩容

---

12.不满足,结束

 1.7.HashMap的key使用什么类型

使用String类型,因为String类型不可变,每次修改都是创建一个新的对象,那么就保证的key的唯一性和安全性

1.8.HashMapkey可以为null的原因

就是说它底层在进行key的哈希运算时会先进行一个判断,判断key是否为null,为null那么直接赋值hash为0,不进行hash运算,并且由于key是唯一的,保证了只能有一个key为null,当然value没有要求

细节:如果你的hashMap没有进行初始化,那么你进行key赋值为null时会出现空指针异常

1.9.HashMap为什么不采用平衡二叉树

平衡二叉树:它追求过度平衡,它需要左右子树的左右节点长度不能超过1,那么这个要求就很高,你如果进行一个高插入和删除的操作,一定会破坏该规则,那么它就需要进行左旋和右旋来平衡数,这个成本就很高,优点:查询速度快,缺点:维护树平衡的成本高

红黑树:它不追求过度平衡,它只需要树的最长路径不超过最短路径的两倍就行,它牺牲了一部分的查询效率换成了一部分维护树成本,并且我们对它进行一个插入和删除,就不会破坏其规则

1.10.HashMap的负载因子

负载因子 = 实际键值对长度除以数组长度

默认为0.75

为什么设置为这个值?

分析:值高了,代表数组内存使用率高了,那么哈希冲突的概率也高了,值低了,数组的空间利用率低,需要频繁的扩容,非常影响性能

1.11.HashTable介绍

它底层数据结构采用数组加链表,数组初始容量为11,每次扩容为2n+1,它采用了synchronized对其方法加锁,使得该集合线程安全,不过由于锁的是整个方法,性能不高

1.12.ConcurrentHashMap的原理

简单来说:jdk1.7采用分段锁的形式将数据分为一段段进行加锁,并发执行,既保证了线程安全也提高了性能,jdk1.8采用CAS与volatile或synchronized方式进行加锁

具体来说:

jdk1.7:

采用数组加链表,数组分为两个数组,一个大数组,一个小数组,大数组为Segment,小数组为HashEntry,Segment其实就是一个可重入锁,而HashEntry数组可以看作一个hashMap,就是说一个ConcurrentHashMap包含一个Segment数组,一个Segment元素包含一个HashEntry数组,而HashEntry就是存储键值对的链表元素,因此是根据Segment来进行分段加锁

jdk1.8: 

采用数组加链表或红黑树,引入红黑树还是增加效率,并且1.8使用的是乐观锁加悲观锁,

简单来说,当你添加元素时,会判断该容器是否为空,为空,代表只需要进行存入即可,这个操作的线程竞争压力不大,采用CAS(乐观锁)+volatile(所有线程都可见并且不可重排序),如果不为空,判断key是否存在,不存在还是一个添加操作采用CAS(乐观锁)+volatile(所有线程都可见并且不可重排序),存在,我们需要进行覆盖操作,线程竞争压力大,使用悲观锁synchronized保证安全

区别:就是说jdk1.8采用的是对头节点进行加锁,锁的粒度更小,并且引入红黑树,大大增加了性能

2.Set集合

2.1.特点

不可重复,元素唯一

2.2.原理

其实就是在计算添加元素的哈希值时,如果发现有哈希值一样的,会使用equals()方法进行内容的判断,如果相同则不存人该值,不同存入即可

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

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

相关文章

【Dify 知识库 API】“根据文本更新文档” 真的是差异更新吗?一文讲透真实机制!

在使用 Dify 知识库 API 过程中,很多开发者在调用 /datasets/{dataset_id}/document/update-by-text 接口时,常常会产生一个疑问: 👉 这个接口到底是 “智能差异更新” 还是 “纯覆盖更新”? 网上的资料并不多,很多人根据接口名误以为是增量更新。今天我结合官方源码 …

大模型如何革新用户价值、内容匹配与ROI预估

写在前面 在数字营销的战场上,理解用户、精准触达、高效转化是永恒的追求。传统方法依赖结构化数据和机器学习模型,在用户价值评估、人群素材匹配以及策略ROI预估等核心问题上取得了显著成就。然而,随着数据维度日益复杂,用户行为愈发多变,传统方法也面临着特征工程繁琐、…

基于端到端深度学习模型的语音控制人机交互系统

基于端到端深度学习模型的语音控制人机交互系统 摘要 本文设计并实现了一个基于端到端深度学习模型的人机交互系统,通过语音指令控制其他设备的程序运行,并将程序运行结果通过语音合成方式反馈给用户。系统采用Python语言开发,使用PyTorch框架实现端到端的语音识别(ASR)…

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…

Jenkins 工作流程

1. 触发构建 Jenkins 的工作流程从触发构建开始。构建可以由以下几种方式触发&#xff1a; 代码提交触发&#xff1a;通过与版本控制系统&#xff08;如 Git、SVN&#xff09;集成&#xff0c;当代码仓库有新的提交时&#xff0c;Jenkins 会自动触发构建。 定时触发&#xff…

Jmeter如何进行多服务器远程测试?

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 JMeter是Apache软件基金会的开源项目&#xff0c;主要来做功能和性能测试&#xff0c;用Java编写。 我们一般都会用JMeter在本地进行测试&#xff0c;但是受到…

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…

分类预测 | Matlab实现CNN-LSTM-Attention高光谱数据分类

分类预测 | Matlab实现CNN-LSTM-Attention高光谱数据分类 目录 分类预测 | Matlab实现CNN-LSTM-Attention高光谱数据分类分类效果功能概述程序设计参考资料 分类效果 功能概述 代码功能 该MATLAB代码实现了一个结合CNN、LSTM和注意力机制的高光谱数据分类模型&#xff0c;核心…

gemini和chatgpt数据对比:谁在卷性能、价格和场景?

先把结论“剧透”给赶时间的朋友&#xff1a;顶配 Gemini Ultra/2.5 Pro 在纸面成绩上普遍领先&#xff0c;而 ChatGPT 家族&#xff08;GPT-4o / o3 / 4.1&#xff09;则在延迟、生态和稳定性上占优。下面把核心数据拆开讲&#xff0c;方便你对号入座。附带参考来源&#xff0…

代码训练LeetCode(23)随机访问元素

代码训练(23)LeetCode之随机访问元素 Author: Once Day Date: 2025年6月5日 漫漫长路&#xff0c;才刚刚开始… 全系列文章可参考专栏: 十年代码训练_Once-Day的博客-CSDN博客 参考文章: 380. O(1) 时间插入、删除和获取随机元素 - 力扣&#xff08;LeetCode&#xff09;力…

C++面试5——对象存储区域详解

C++对象存储区域详解 核心观点:内存是程序员的战场,存储区域决定对象的生杀大权!栈对象自动赴死,堆对象生死由你,全局对象永生不死,常量区对象只读不灭。 一、四大地域生死簿 栈区(Stack) • 特点:自动分配释放,速度极快(类似高铁进出站) • 生存期:函数大括号{}就…

STM32 智能小车项目 L298N 电机驱动模块

今天开始着手做智能小车的项目了 在智能小车或机器人项目中&#xff0c;我们经常会听到一个词叫 “H 桥电机驱动”&#xff0c;尤其是常见的 L298N 模块&#xff0c;就是基于“双 H 桥”原理设计的。那么&#xff0c;“H 桥”到底是什么&#xff1f;为什么要用“双 H 桥”来驱动…

python项目如何创建docker环境

这里写自定义目录标题 python项目创建docker环境docker配置国内镜像源构建一个Docker 镜像验证镜像合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPant…

MySQL-多表关系、多表查询

一. 一对多(多对一) 1. 例如&#xff1b;一个部门下有多个员工 在数据库表中多的一方(员工表)、添加字段&#xff0c;来关联一的一方(部门表)的主键 二. 外键约束 1.如将部门表的部门直接删除&#xff0c;然而员工表还存在其部门下的员工&#xff0c;出现了数据的不一致问题&am…

【 HarmonyOS 5 入门系列 】鸿蒙HarmonyOS示例项目讲解

【 HarmonyOS 5 入门系列 】鸿蒙HarmonyOS示例项目讲解 一、前言&#xff1a;移动开发声明式 UI 框架的技术变革 在移动操作系统的发展历程中&#xff0c;UI 开发模式经历了从命令式到声明式的重大变革。 根据华为开发者联盟 2024 年数据报告显示&#xff0c;HarmonyOS 设备…

【SSM】SpringMVC学习笔记7:前后端数据传输协议和异常处理

这篇学习笔记是Spring系列笔记的第7篇&#xff0c;该笔记是笔者在学习黑马程序员SSM框架教程课程期间的笔记&#xff0c;供自己和他人参考。 Spring学习笔记目录 笔记1&#xff1a;【SSM】Spring基础&#xff1a; IoC配置学习笔记-CSDN博客 对应黑马课程P1~P20的内容。 笔记2…

借助 Spring AI 和 LM Studio 为业务系统引入本地 AI 能力

Spring AI 1.0.0-SNAPSHOTLM Studio 0.3.16qwen3-4b 参考 Unable to use spring ai with LMStudio using spring-ai openai module Issue #2441 spring-projects/spring-ai GitHub LM Studio 下载安装 LM Studio下载 qwen3-4b 模型。对于 qwen3 系列模型&#xff0c;测试…

C++学习-入门到精通【13】标准库的容器和迭代器

C学习-入门到精通【13】标准库的容器和迭代器 目录 C学习-入门到精通【13】标准库的容器和迭代器一、标准模板库简介1.容器简介2.STL容器总览3.近容器4.STL容器的通用函数5.首类容器的通用typedef6.对容器元素的要求 二、迭代器简介1.使用istream_iterator输入&#xff0c;使用…

Vue Router的核心实现原理深度解析

1. Vue Router的基本架构 Vue Router的核心功能是实现前端路由&#xff0c;即在不重新加载页面的情况下更改应用的视图。它的基本架构包括&#xff1a; 路由配置&#xff1a;定义路径与组件的映射关系路由实例&#xff1a;管理路由状态和提供导航方法路由视图&#xff1a;渲染…

设计模式——状态设计模式(行为型)

摘要 状态设计模式是一种行为型设计模式&#xff0c;核心在于允许对象在内部状态改变时改变行为。它通过状态对象封装不同行为&#xff0c;使状态切换灵活清晰。该模式包含环境类、抽象状态类和具体状态类等角色&#xff0c;具有避免大量分支判断、符合单一职责和开闭原则等特…