当前位置: 首页 > news >正文

JavaSE从零开始到精通(九) - 双列集合

 1.前言

Java 中的双列集合主要指的是可以存储键值对的集合类型,其中最常用的包括 Map 接口及其实现类。这些集合允许你以键值对的形式存储和管理数据,提供了便捷的按键访问值的方式。

2. HashMap 

HashMap 是基于哈希表实现的 Map 接口的类,允许存储键值对,支持 null 键和 null 值。它提供了 O(1) 时间复杂度的插入、删除和查找操作,但不保证映射顺序。

由键决定:无序、不重复、无索引。

底层数据结构: 数组 + 链表 / 红黑树(JDK8+)其中一个链表超过8,并且数组长度不低于64转换为红黑树。通过哈希函数将键映射到数组索引,解决哈希冲突的链表或红黑树存储。初始容量是16,扩容因子是0.75。扩容扩大两倍(持续保持2^n次方的容量)。

多个输入对应相同输出:hash冲突

为什么要转换成红黑树?

HashMap变红黑树主要用于防患于未然,防止有些人造一些恶劣数据。正常存20万个单词都不会有链表超过8。

为什么hash表的容量是2^n次方?

1.能使用2^n

对于程序来说涉及位运算符相对来说都是高效的,因为计算机底层是以二进制存储的,二进制是天然的位运算符操作容器。 

2.高低位异或计算hash值

因为二进制和容器容量正好对应,只需要看后几位,高位根本用不上。2^n次方看后n位。

低位相同的哈希码,在高位不同的情况下,会产生hash冲突。通过高16位和低16位异或运算能够让存入的位置由高位的参与决定。

负载因子的概念

负载因子(load factor)是哈希表的一个重要概念,其定义为哈希表的元素数量除以桶数量,用于衡量哈希冲突的严重程度,也常作为哈希表扩容的触发条件。例如在 Java 中,当负载因子超过 0.75 时,系统会将哈希表扩容至原先的 2 倍。

容易想到,哈希表容量 𝑛 越大,多个 key 被分配到同一个桶中的概率就越低,冲突就越少。因此,我们可以通过扩容哈希表来减少哈希冲突。

类似于数组扩容,哈希表扩容需将所有键值对从原哈希表迁移至新哈希表,非常耗时;并且由于哈希表容量 capacity 改变,我们需要通过哈希函数来重新计算所有键值对的存储位置,这进一步增加了扩容过程的计算开销。为此,编程语言通常会预留足够大的哈希表容量,防止频繁扩容。

HashSet 的加载因子被设置为 0.75 是为了在空间利用率和性能之间取得平衡。这个值能够使得哈希表在大多数情况下既能够有效地利用内存,又能够保持较好的查询性能,是经过权衡和实验确定的合理选择。

// 创建一个HashMap
Map<String, Integer> hashMap = new HashMap<>();// 添加键值对
hashMap.put("apple", 1);
hashMap.put("banana", 2);// 获取值
int value = hashMap.get("apple");
System.out.println(value); // 输出: 1

3. LinkedHashMap:

  • LinkedHashMap 继承自 HashMap,保留了元素插入顺序,即按照插入顺序迭代元素。可选择按访问顺序(LRU 缓存算法)排序。

  • 由键决定:有序、不重复、无索引。

    底层数据结构: 哈希表 + 双向链表。双向链表维护插入顺序或访问顺序。

 

与HashMap不同的是:存和取的顺序相同

原理:每个键值对元素之间又额外的多了一个双链表的机制记录存储的顺序。 

 在添加第一个元素的时候,会记录第一个元素的地址值,然后取的时候根据第一个元素的地址值找到头节点,依次进行取元素。

// 创建一个LinkedHashMap,保持插入顺序
Map<String, Integer> linkedHashMap = new LinkedHashMap<>(16, 0.75f, true);// 添加键值对
linkedHashMap.put("apple", 1);
linkedHashMap.put("banana", 2);// 获取值,更新访问顺序
int value = linkedHashMap.get("apple");
System.out.println(value); // 输出: 1

4. TreeMap:

  • TreeMap 基于红黑树实现,可根据键的自然顺序或自定义比较器排序。元素有序存储,提供灵活的自定义排序功能。

    底层数据结构: 红黑树。自平衡二叉查找树,保证有序性。

 对于原理和TreeSet相似,在上一篇已经讲解过了,这里就不过多介绍了。

// 创建一个TreeMap,根据键的自然顺序排序
Map<String, Integer> treeMap = new TreeMap<>();// 添加键值对
treeMap.put("apple", 1);
treeMap.put("banana", 2);// 获取值
int value = treeMap.get("apple");
System.out.println(value); // 输出: 1

5. Hashtable:

  • Hashtable 是早期的 Map 类,与 HashMap 类似但线程安全。由于同步开销大,推荐使用 ConcurrentHashMap 替代。

    底层数据结构: 数组 + 链表。通过哈希函数将键映射到数组索引,使用链表解决冲突。

// 创建一个Hashtable
Map<String, Integer> hashtable = new Hashtable<>();// 添加键值对
hashtable.put("apple", 1);
hashtable.put("banana", 2);// 获取值
int value = hashtable.get("apple");
System.out.println(value); // 输出: 1

面试题:谈谈HashMap和HashTable的区别

  1. 线程安全方面
    1. HashTable是线程安全的(底层使用了全局同步锁)
    2. HashMap是线程不安全的,因此可以得出HashMap的性能比较好
  2. 数据结构的实现
    1. java8以后HashMap使用数组+链表+红黑树组成(当节点高于7转换为红黑树)

    2. HashTable是数组+链表

  3. 初始容量
    1. HashMap初始容量是16
    2. HashTable初始容量是11
  4. hash算法
    1. HashMap的散列算法是对key的hashcode进行二次散列,从而避免key的分布不均匀影响性能。
    2. HashTable是hashcode对数组的长度取模

6. ConcurrentHashMap:

  • ConcurrentHashMap 针对高并发设计,通过分段锁(Segmented lock)实现线程安全。性能优于 Hashtable,在多线程环境中使用较为合适。

    底层数据结构: 分段锁 + 数组 + 链表 / 红黑树(JDK8+)。每个段(Segment)独立锁定,允许多线程并发访问不同段的数据。

// 创建一个ConcurrentHashMap
Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();// 添加键值对
concurrentHashMap.put("apple", 1);
concurrentHashMap.put("banana", 2);// 获取值
int value = concurrentHashMap.get("apple");
System.out.println(value); // 输出: 1

这些双列集合类在不同的场景中提供了各自的优势,根据需求选择合适的实现类可以提高数据的管理效率和性能。

面试题:HashTable为什么会被ConcurrentHashMap替代

ConcurrentHashMap 可以替代 Hashtable 的几个主要原因包括:

  1. 并发性能优化: ConcurrentHashMap 使用了分段锁(Segmented lock)或 CAS 操作来实现线程安全,而 Hashtable 使用了全局锁。这意味着 ConcurrentHashMap 在多线程并发访问时,可以支持更高的并发度,因为不同段(Segment)的数据可以并行操作,而 Hashtable 的所有操作都是同步的。

  2. 更好的扩展性: ConcurrentHashMap 在设计上采用了分段锁策略,这样在多线程情况下,不同段的数据可以并行处理,从而减少了锁竞争,提高了并发访问效率。相比之下,Hashtable 的同步操作可能会导致线程争用,限制了扩展性和性能。

  3. 更好的迭代性能: 在迭代时,ConcurrentHashMap 可以允许在不进行任何同步的情况下进行读取操作,而 Hashtable 则要求在整个表上进行锁定。这意味着在迭代期间,ConcurrentHashMap 的性能更好,因为它不会被其他线程的修改所阻塞。

  4. 允许空键和空值: ConcurrentHashMap 允许 null 键和 null 值的存在,而 Hashtable 不允许。这在某些场景下可能更为灵活。

综上所述,ConcurrentHashMap 在设计和实现上优于 Hashtable,尤其是在高并发环境中能够提供更好的性能和扩展性,因此在大多数情况下推荐使用 ConcurrentHashMap 而不是 Hashtable。

 7. Peoperties

Properties 是 Java 中处理配置文件的一个常用类,它继承自 Hashtable<Object, Object>,也实现了 Map<Object, Object> 接口。主要用于存储键值对形式的配置信息,通常用于读取和写入属性文件(.properties 文件)。 

Properties 类在 Java 中广泛应用于配置文件的读写

使用load()方法从一个输入流中加载属性,或者使用store()方法将属性保存到输出流中。

主要特点和用途:

  1. 存储键值对: Properties 对象存储的是字符串键和值的映射关系,键和值都必须是字符串类型。

  2. 加载和存储属性文件: Properties 可以从文件中加载配置信息,也可以将配置信息写入文件。常见的配置文件格式是 .properties 文件,它采用 key=value 的格式存储配置信息。

  3. 默认的读写方法: 提供了便捷的方法来加载和存储属性文件,如 load()store() 方法。

  4. 默认值支持: 可以设置默认值,当请求的属性不存在时,返回默认值。

  5. 线程安全性: Properties 继承自 Hashtable,因此默认不是线程安全的。在多线程环境下,可以通过同步措施来保证线程安全。

基本操作示例:

创建和设置属性:
Properties prop = new Properties();
prop.setProperty("database.url", "jdbc:mysql://localhost:3306/mydatabase");
prop.setProperty("database.user", "username");
prop.setProperty("database.password", "password");
从文件加载属性: 
try (FileInputStream fis = new FileInputStream("config.properties")) {prop.load(fis);
} catch (IOException e) {e.printStackTrace();
}
 将属性存储到文件:
try (FileOutputStream fos = new FileOutputStream("config.properties")) {prop.store(fos, "Database Configuration");
} catch (IOException e) {e.printStackTrace();
}
 获取属性值:
String url = prop.getProperty("database.url");
String user = prop.getProperty("database.user", "defaultUser"); // 指定默认值
String password = prop.getProperty("database.password");
注意事项:
  1. 字符编码:默认情况下,store() 方法使用 ISO 8859-1 编码,不支持 Unicode 字符。如果需要支持 Unicode,可以使用 Writer 参数的 store(Writer writer, String comments) 方法,并指定合适的字符编码。
  2. 属性文件格式: .properties 文件使用简单的 key=value 格式存储数据,不支持复杂的数据结构或注释(可以用 # 开头注释)。
  3. 默认值处理: 如果请求的属性不存在,getProperty(key) 方法返回 null;可以使用 getProperty(key, defaultValue) 指定默认值。

 8. 总结

        双列集合在软件开发中扮演关键角色,通过键值对的方式存储和管理数据,支持快速的插入、删除和查找操作,适用于各种应用场景,如配置管理、缓存实现和关联数据管理。Java中的Map接口及其实现类(如HashMap、TreeMap等)提供了多样化选择,开发人员可以根据需求选择最适合的实现以提高性能和效率。这些特性使得双列集合成为处理复杂数据结构和优化数据访问的重要工具。

相关文章:

JavaSE从零开始到精通(九) - 双列集合

1.前言 Java 中的双列集合主要指的是可以存储键值对的集合类型&#xff0c;其中最常用的包括 Map 接口及其实现类。这些集合允许你以键值对的形式存储和管理数据&#xff0c;提供了便捷的按键访问值的方式。 2. HashMap HashMap 是基于哈希表实现的 Map 接口的类&#xff0c…...

探索 OpenAI GPT-4o Mini:开发者的高效创新工具

探索 OpenAI GPT-4o Mini&#xff1a;开发者的高效创新工具 最近&#xff0c;OpenAI 推出了全新的 GPT-4o Mini 模型&#xff0c;以其出色的性能和极具吸引力的价格&#xff0c;引起了开发者们的广泛关注。作为开发者&#xff0c;你是否已经开始探索这个“迄今为止最具成本效益…...

藏文词典查单词,藏汉双语解释,推荐使用《藏语翻译通》App

《藏语翻译通》App推出了藏文词典、藏汉大词典、新术语等全新在线查单词功能。 藏汉互译 《藏语翻译通》App的核心功能之一是藏汉互译。用户只需输入中文或藏文&#xff0c;即可获得翻译结果。 藏文词典查单词 掌握一门语言&#xff0c;词汇是基础。《藏语翻译通》App内置藏…...

【机器学习基础】初探机器学习

【作者主页】Francek Chen 【专栏介绍】⌈Python机器学习⌋ 机器学习是一门人工智能的分支学科&#xff0c;通过算法和模型让计算机从数据中学习&#xff0c;进行模型训练和优化&#xff0c;做出预测、分类和决策支持。Python成为机器学习的首选语言&#xff0c;依赖于强大的开…...

SpringBoot轻松实现多数据源切换

一.需求背景 项目需要实现在多个数据源之间读写数据&#xff0c;例如在 A 数据源和 B 数据源读取数据&#xff0c;然后在 C 数据源写入数据 或者 部分业务数据从 A 数据源中读取、部分从B数据源中读取诸如此类需求。本文将简单模拟在SpringBoot项目中实现不同数据源之间读取数…...

Qt 5 当类的信号函数和成员函数,函数名相同时,连接信号和槽的写法。

前言&#xff1a;因为项目需要&#xff0c;软件要在windows7上运行&#xff0c;然后项目目前是qt6写的&#xff0c;然后搜索资料&#xff0c;需要qt5.15.2或之前的版本才能在win7上运行&#xff0c;于是下载了qt5.15.2&#xff0c;将qt6的代码在qt5编译时&#xff0c;很多错误&…...

Vuex 介绍及示例

Vuex 是 Vue.js 的一个状态管理模式和库&#xff0c;用于管理 Vue 应用中的全局状态。它是专门为 Vue.js 应用设计的&#xff0c;充分利用了 Vue 的细粒度响应系统来高效地更新状态。以下是对 Vuex 的一些介绍和它的基本使用方法&#xff1a; 主要概念 State&#xff08;状态&…...

【elementui】记录如何重命名elementui组件名称

在main.js中&#xff0c;就是引入elementui的文件中 import ElementUI from element-ui import { Tree } from element-uiVue.use(ElementUI) Vue.component(el-tree-rename, Tree)...

MySQL面试篇章—MySQL锁机制

文章目录 MySQL的锁机制表级锁 & 行级锁排它锁和共享锁InnoDB行级锁行级锁间隙锁意向共享锁和意向排它锁 InnoDB表级锁死锁锁的优化建议MVCC多版本并发控制MyISAM表级锁表级锁并发插入优化锁调度优化 MySQL的锁机制 表级锁 & 行级锁 表级锁&#xff1a;对整张表加锁&…...

OAK相机支持的图像传感器有哪些?

相机支持的传感器 在 RVC2 上&#xff0c;固件必须具有传感器配置才能支持给定的相机传感器。目前&#xff0c;我们支持下面列出的相机传感器的开箱即用&#xff08;固件中&#xff09;传感器配置。 名称 分辨率 传感器类型 尺寸 最大 帧率 IMX378 40563040 彩色 1/2.…...

网络安全威胁情报是什么,它对代工生产(OEM)意味着什么?

随着汽车数字环境的不断变化&#xff0c;网络安全基础设施及其面临的威胁也日趋复杂。 为了更好地识别、理解并最终预防这些风险&#xff0c;网络安全威胁情报&#xff08;CTI&#xff09;的管理应是一个综合多方面的过程。 以下是CTI对OEM的意义&#xff0c;以及如何利用网络…...

【基础篇】Docker 架构与组件 TWO

嗨&#xff0c;小伙伴们&#xff01;我是小竹笋&#xff0c;一名热爱创作的工程师。上一篇我们聊了聊 Docker 的历史与发展、与虚拟机的对比以及它在行业中的应用。今天&#xff0c;让我们更进一步&#xff0c;深入探讨 Docker 的架构与关键组件。 欢迎订阅公众号&#xff1a;…...

03。正式拿捏ArkTS语言第一天

1, 打印日志命令 &#xff1a; console.log() 2, 三种基本数据类型&#xff1a; number 数字类型 &#xff08;数字&#xff09; string 字符串类型&#xff08;例如&#xff1a;“我是字符串”&#xff09; boolean 布尔类型 (true 或者 false) ***…...

【PyTorch][chapter 27][李宏毅深度学习][attention-3]

前言&#xff1a; 前面重点讲了self-attention, mulitHead self-attention. 目录&#xff1a; self-attention positional Encoding 语音处理例子 跟CNN区别 跟 RNN 区别 一 self-attention 回顾 优点 1 解决了长序列依赖问题 2 并行计算 缺点 1 开销变大 增加了 Q…...

java-数据结构与算法-02-数据结构-05-栈

文章目录 1. 栈1. 概述2. 链表实现3. 数组实现4. 应用 2. 习题E01. 有效的括号-Leetcode 20E02. 后缀表达式求值-Leetcode 120E03. 中缀表达式转后缀E04. 双栈模拟队列-Leetcode 232E05. 单队列模拟栈-Leetcode 225 1. 栈 1. 概述 计算机科学中&#xff0c;stack 是一种线性的…...

Python 管理依赖包(pip, virtualenv)

在Python编程中&#xff0c;管理依赖包是开发工作的重要组成部分。正确管理依赖包可以确保代码在不同环境中的一致性和可移植性&#xff0c;避免版本冲突和依赖地狱等问题。Python中常用的依赖包管理工具包括pip和virtualenv。 一、pip pip是Python官方推荐的包管理工具&…...

Bigdecimal 导出为excel时显示未0E-10,不是0,怎么解决

在使用 ​​BigDecimal​​​ 导出到 Excel 时&#xff0c;如果遇到显示为 ​​0E-10​​​ 而不是 ​​0​​​ 的问题&#xff0c;这通常是因为 ​​BigDecimal​​​ 对象的精度问题。​​0E-10​​​ 表示的是 ​​0​​​ 乘以 10 的 -10 次方&#xff0c;这在数学上等同于…...

springboot项目从jdk8升级为jdk17过程记录

背景&#xff1a;公司有升级项目jdk的规划&#xff0c;计划从jdk8升级到jdk11 开始 首先配置本地的java_home 参考文档&#xff1a;Mac环境下切换JDK版本及不同的maven-CSDN博客 将pom.xml中jdk1.8相关的版本全部改为jdk17&#xff0c;主要是maven编译插件之类的&#xff0c…...

list、tuple、set和dict传参机制

1、list、tuple、set和dict传参机制 # -------------list------------- def f1(my_list):print(f"②f1()my_list:{my_list} 地址是&#xff1a;{id(my_list)}") # ["tom","mary","hsp"] 0x1122my_list[0]"jack"print(f&quo…...

Redis快速入门基础

Redis入门 Redis是一个基于内存的 key-value 结构数据库。mysql是二维表的接口数据库 优点&#xff1a; 基于内存存储&#xff0c;读写性能高 适合存储热点数据(热点商品、资讯、新闻) 企业应用广泛 官网:https://redis.io 中文网:https://www.redis.net.cn/ Redis下载与…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

基于服务器使用 apt 安装、配置 Nginx

&#x1f9fe; 一、查看可安装的 Nginx 版本 首先&#xff0c;你可以运行以下命令查看可用版本&#xff1a; apt-cache madison nginx-core输出示例&#xff1a; nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...