Java集合的底层原理

news2025/3/19 12:24:02

目录

Collection

Arraylist

HashSet

介绍

哈希值

哈希表的基本概念

HashSet 的内部实现

HashMap

哈希碰撞的处理

总结

TreeSet

特点

红黑树的特性

红黑规则

TreeSet 的内部实现

1. 存储结构

2. 添加元素(重点)

3. 查找元素

4. 删除元素

总结

几个问题思考


Collection

  1. List
    • Arraylist: Object数组
    • Vector: Object数组
    • LinkedList: 双向链表
  1. Set
    • HashSet(无序,唯一):基于 HashMap 实现的底层采用 HashMap 来保存元素
    • LinkedHashSet: LinkedHashSet 继承于 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
    • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)
  • Map
  • HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
  • LinkedHashMap(存取有序):LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。

  • HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
  • TreeMap(可排序): 红黑树(自平衡的排序二叉树)

存取有序:存和取的元素顺序相同。

Arraylist

注:扩容时会创建一个新数组,会先将原数组的数据拷贝到新数组当中。。。

HashSet

介绍

HashSet 的底层实现主要依赖于 HashMap。在 Java 中,HashSet 的实现是通过内部维护一个 HashMap 实例来完成的,而 HashMap 则是基于哈希表的数据结构。哈希表是一种使用哈希函数来快速访问数据的数据结构,它通过把键映射到表中一个位置来访问记录,以加快查找的速度。

哈希值

重写自定义类的 equals 方法以及 hashCode 方法:

举例(Student 类):

哈希表的基本概念

  1. 哈希函数:哈希表首先会定义一个哈希函数,用于将键转换为数组索引。在 Java 的 HashSet 中,这个哈希函数会使用hashCode() 方法来生成一个哈希码。
  2. 数组 + 链表/红黑树:哈希表通常由一个数组组成,数组的每个槽位(slot)或称为桶(bucket),可以存储一个或多个元素。如果两个不同的键通过哈希函数产生了相同的索引(哈希碰撞),那么这些键将以链表的形式存储在同一个桶中。在 Java 8 之后,如果链表长度超过一定阈值(默认为8),链表会转换为红黑树,以减少查找时间。

HashSet 的内部实现

注:

当数组存储的数据达到 (0.75*数组长度) 时,会对数组进行2倍扩容。

链表长度大于8而且数组长度大于等于64 时:链表自动转成红黑树。

如果集合中存储的是自定义对象,必须要重写hashcodeequals方法!!!

  1. 存储结构HashSet 内部维护了一个 HashMap 实例,这个 HashMap 的每个键值对中的值都是一个固定的对象(在 HashSet 的实现中,这个对象并不重要,通常是一个名为 PRESENT 的静态 final 对象)。
  2. 添加元素:当向 HashSet 添加一个元素时,首先会调用这个元素的 hashCode() 方法来计算它的哈希码(哈希值),然后根据哈希码找到对应的桶。如果这个桶中没有元素,或者元素与要添加的元素相等(通过 equals() 方法比较),则将元素添加到桶中。如果发生哈希碰撞,新元素将被添加到链表的末尾,或者添加到红黑树中。
  3. 查找元素:当要查找一个元素时,HashSet 会使用同样的哈希函数来计算元素的哈希码,然后到对应的桶中查找。如果找到了相等的元素,则返回 true。如果桶中有多个元素(因为哈希碰撞),那么会使用 equals() 方法来逐个比较链表或红黑树中的元素。
  4. 删除元素:删除元素的过程与查找类似,首先找到元素所在的桶,然后从链表或红黑树中删除元素。

HashMap

HashSet 内部本质是维护了一个 HashMap 实例,所以它们的底层基本相同,

唯一不同的地方:

当用 equals 方法比较属性值之后,如果一样的话,HashSet 底层是不存新元素,而 HashMap 的底层是覆盖(新加的元素覆盖老的元素)。

其实 HashSet 的底层也是覆盖,只是因为它不存和覆盖的效果一样,而 HashMap 键一样但是值可能不一样,就会出现覆盖的效果。

哈希碰撞的处理

哈希碰撞是哈希表中的一个重要概念,当两个不同的键产生了相同的哈希码时,就会发生哈希碰撞。Java 的 HashSet 通过链表或红黑树来解决哈希碰撞:

  • 链表:在发生碰撞的情况下,新元素将被添加到链表的末尾。如果链表变得过长,那么它的性能会下降,因为查找时间会变长。
  • 红黑树:在 Java 8 中,如果一个桶中的链表长度超过阈值(默认为8),链表会被转换成红黑树。红黑树是一种自平衡的二叉搜索树,它能够保持树的平衡,从而保证查找、插入和删除的最坏情况时间复杂度为 O(log n)。

总结

HashSet 的底层哈希表实现提供了高效的添加、删除和查找操作。它的性能取决于哈希函数的质量、哈希碰撞的处理以及负载因子的设置。负载因子决定了哈希表什么时候进行扩容,默认值为 0.75,这意味着当哈希表中的元素数量达到数组大小的 75% 时,哈希表会进行扩容操作,以减少哈希碰撞的几率。

TreeSet

TreeSet 是 Java 中 SortedSet 接口的一个实现,它基于红黑树 数据结构来存储元素。红黑树是一种自平衡的二叉搜索树,它能够保持树的平衡,从而保证最坏情况下的查找、插入和删除操作的时间复杂度为 O(log n)。下面是 TreeSet 底层实现的详细介绍:

特点

可排序,不重复,无索引

红黑树的特性

红黑树具有以下五个主要特性:

  1. 每个节点包含一个颜色属性:节点可以是红色或黑色。
  2. 根节点是黑色:红黑树的根节点必须是黑色。
  3. 红色节点不能连续:红黑树中不能有两个连续的红色节点,即红色节点的子节点和父节点不能是红色。
  4. 从任一节点到其每个叶子节点的所有路径都包含相同数量的黑色节点:这意味着从根节点到叶子节点的所有路径上的黑色节点数目是相同的,这个属性称为“黑色完美性”。
  5. 新插入的节点是红色:新插入的节点默认为红色,以保持其他红黑树性质的稳定性。

红黑规则

TreeSet 的内部实现

1. 存储结构

TreeSet 内部维护了一个 NavigableMap 实例,默认情况下这个实例是 TreeMap。TreeMap 的底层实现就是红黑树,它存储了排序后的键值对,而 TreeSet 实际上只是使用了 TreeMap 的键来存储元素,值则是一个固定的对象(通常是一个静态的 final 对象)

2. 添加元素(重点)

当向 TreeSet 添加一个元素时,TreeMap 会根据元素的比较结果(自然排序或者比较器排序)来确定元素的存储位置。如果元素(自定义的类)实现了 Comparable 接口,那么会使用这个接口的方法来比较元素。如果没有实现 Comparable,那么在创建 TreeSet 集合时必须自定义Comparator比较器对象来定义元素的排序规则。添加元素时,红黑树会进行颜色变更和旋转操作,以保持树的平衡。

也可以分为以下两种情况:

Java写好的类
//1.自然排序/默认排序
//Integer Double  默认情况下按照升序排列(Java的内部源码)
//String  按照字母在ASCll码表中对应的数字升序排列
//2.比较器排序
//创建集合时传递Comparator比较器对象,指定比较规则
自定义的类
//1.默认排序/自然排序
//自定义类实现Comparable接口,再重写compareTo方法(用于指定排序规则)
//2.比较器排序
//创建集合时传递Comparator比较器对象,指定比较规则

代码举例:

如:(比较器排序)

import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetExample {
    public static void main(String[] args) {
        //使用匿名内部类创建 TreeSet,并传入自定义的比较器
        TreeSet<Integer> ts1 = new TreeSet<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                //o1:当前要添加的元素
                //o2:表示已经在红黑树中存在的元素
                return o2 - o1;//实现降序功能

                //o2 - o1 可能导致整数溢出,
                //return Integer.compare(o2, o1); //这个是更安全的降序比较
            }
        });
        //使用Lambda表达式创建 TreeSet,并传入自定义的比较器
        TreeSet<Integer> ts2 = new TreeSet<>((o1, o2) -> o2 - o1);
        
        // 向 TreeSet 中添加元素
        ts1.add("Apple");
        ts1.add("Banana");
        ts1.add("Cherry");
        ts1.add("Date");
        // 输出 TreeSet 中的元素
        System.out.println(ts1);
    }
}

如:(自然排序、默认排序)

public class Student2 implements Comparable<Student2> {
    
    private String name;
    private int age;
    
    public Student2() {}
    public Student2(String name, int age) {
        this.name = name;
        this.age = age;}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public int getAge() {return age;}
    public void setAge(int age) {this.age = age;}
    public String toString() {
        return "Student2{name = " + name + ", age = " + age + "}";
    }

    //自定义类实现Comparable接口,再重写compareTo方法(用于指定排序规则)
    @Override
    public int compareTo(Student2 o) {
        //this:当前要添加的元素
        //o:表示已经在红黑树中存在的元素
        //返回值:
        //负数:表示当前要添加的元素是小的,存左边
        //正数:表示当前要添加的元素是大的,存右边
        //0:覆盖
        int i = this.getAge() - o.getAge();//按照年龄升序排序
        //年龄一样按照姓名的字母排序
        i = i == 0 ? this.getName().compareTo(o.getName()) : i;
        return i;
    }
}

自然排序和比较器排序的合理使用:

  • 使用自然排序:当集合中的元素类型已经实现了 Comparable 接口,并且你希望使用这个排序规则时,可以选择自然排序。这是最简单的情况,因为你不需要提供额外的比较器。
  • 使用比较器排序:当集合中的元素类型没有实现 Comparable 接口,或者你希望使用不同于自然顺序的排序规则时,应该提供一个自定义的 Comparator。这允许你根据特定的业务逻辑或者需求来定义排序规则。
  • 注意事项:在使用 TreeSet 时,确保添加到集合中的所有元素都是可比较的,无论是通过自然排序还是通过比较器排序。如果元素不可比较,或者在比较时抛出 ClassCastException,那么在添加元素时会抛出异常。
  • 性能考虑比较器排序可能会稍微降低性能,因为它需要使用额外的 Comparator 对象来进行比较。如果性能是关键考虑因素,并且可以接受自然排序,那么应该优先使用自然排序。

3. 查找元素

查找元素时,TreeSet 会使用红黑树的搜索性质来定位元素。由于树是平衡的,所以查找操作的时间复杂度为 O(log n)。

4. 删除元素

删除元素时,TreeSet 会先找到要删除的节点,然后根据不同情况执行删除操作,并可能需要进行颜色变更和旋转操作,以保持红黑树的性质。

总结

TreeSet 的底层红黑树实现提供了有序的集合存储,并且保证了高效的添加、删除和查找操作。由于红黑树的自平衡特性,TreeSet 能够在元素动态变化的情况下保持良好的性能。与基于哈希表的 HashSet 相比,TreeSet 在元素顺序和排序功能上提供了更多的支持,但可能在某些操作上稍微慢一些,尤其是在最坏情况下的性能。

几个问题思考

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

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

相关文章

SPI驱动(九) -- SPI_Master驱动程序

文章目录 参考资料&#xff1a;一、SPI传输概述二、SPI传输的两种方法2.1 旧方法2.2 新方法 参考资料&#xff1a; 参考资料&#xff1a; 参考内核源码: drivers\spi\spi.c 一、SPI传输概述 SPI控制器的作用是发起与它下面挂接的SPI设备之间的数据传输&#xff0c;那么控制…

Linux 进程的创建、终止、等待与程序替换函数 保姆级讲解

目录 一、 进程创建 fork函数 二、进程的终止&#xff1a; 1. 想明白&#xff1a;终止是在做什么&#xff1f; 2.进程终止的3种情况&#xff1f; a.退出码是什么&#xff1f;存在原因&#xff1f;为什么int main&#xff08;&#xff09;return 0? b.第三种进程终止的情况…

BSCAN2-1:load design

1. DFT Flow Using Tessent Shell Tessent BoundaryScan 具有一个基本的高层次流程顺序。下图展示了将 Tessent BoundaryScan 插入设计所需的高层次步骤顺序。图中的每个步骤都链接到有关可测试性设计&#xff08;DFT&#xff09;流程的更详细信息&#xff0c;包括示例。 Desi…

【css酷炫效果】纯CSS实现立体旋转立方体

【css酷炫效果】纯CSS实现立体旋转立方体 缘创作背景html结构css样式完整代码效果图 想直接拿走的老板&#xff0c;链接放在这里&#xff1a;https://download.csdn.net/download/u011561335/90492014 缘 创作随缘&#xff0c;不定时更新。 创作背景 刚看到csdn出活动了&am…

LLM中lora的梯度更新策略公式解析

LLM中lora的梯度更新策略公式解析 目录 LLM中lora的梯度更新策略公式解析区别如何使用LoRA代码中的参数更新方式二阶导数(如右侧公式关联的Fisher信息)的作用区别 定义与理论来源: 左公式 F ( w i ) = 1 n

Mac下Ollama安装全攻略:开启本地大模型之旅

文章目录 Mac下Ollama安装全攻略&#xff1a;开启本地大模型之旅一、Ollama 是什么功能特点优势应用场景 二、安装前准备&#xff08;一&#xff09;系统要求&#xff08;二&#xff09;硬件要求 三、下载安装包&#xff08;一&#xff09;官网下载&#xff08;二&#xff09;其…

线程大乱斗:从入门到精通,解锁Java并发编程的终极秘籍

目录 什么是线程&#xff1f; jave创建线程方式有几种&#xff1f; 线程中常用的方法 线程状态 多线程 解决线程安全问题 线程通信 何为并发编程&#xff1f; 并发执行和并行执行 线程的三个主要问题&#xff1a; 1、不可见性&#xff1a; 2、乱序性&#xff1a; …

Web3游戏行业报告

一&#xff0c;gamefi经济 什么是gamefi GameFi是一个缩写&#xff0c;它结合了游戏和去中心化金融(“DeFi”)这两个术语&#xff0c;关注的是游戏玩法如何在去中心化系统中实现货币化。对于游戏而言&#xff0c;只要开放了交易市场&#xff0c;允许玩家自由买卖&#xff0c;…

hibernate 自动生成数据库表和java类 字段顺序不一致 这导致添加数据库数据时 异常

hibernate 自动生成的数据库表和java类 字段顺序不一致 这导致该书写方式添加数据库数据时 异常 User user new User( null, username, email, phone, passwordEncoder.encode(password) ); return userRepository.save(user);Hibernate 默认不会保证数据库表字段的顺序与 Ja…

Cursor在内网环境配置自定义DeepSeek API

关键字 Cursor、DeepSeek、API配置、内网代理、HTTP/2 背景环境 使用Cursor集成环境开发程序。但是我使用公司的内网并不能使用cursor自带的模型&#xff0c;于是我就想使用DeepSeek官方的API服务。 环境&#xff1a;Windows 11系统 解决过程 网络检测 首先进行环境检测&am…

【初学者】解释器和脚本各是什么?有什么区别与联系?

李升伟 整理 解释器和脚本的定义 1. 解释器&#xff08;Interpreter&#xff09; 定义&#xff1a;解释器是一个程序&#xff0c;负责逐行读取并执行代码。它将源代码翻译成机器能理解的指令&#xff0c;并立即执行。特点&#xff1a; 逐行执行代码。适合交互式编程&#xf…

Kafka跨集群数据备份与同步:MirrorMaker运用

#作者&#xff1a;张桐瑞 文章目录 前言MirrorMaker是什么运行MirrorMaker各个参数的含义 前言 在大多数情况下&#xff0c;我们会部署一套Kafka集群来支撑业务需求。但在某些特定场景下&#xff0c;可能需要同时运行多个Kafka集群。比如&#xff0c;为了实现灾难恢复&#x…

设计模式(创建型)-抽象工厂模式

摘要 在软件开发的复杂世界中,设计模式作为解决常见问题的最佳实践方案,一直扮演着至关重要的角色。抽象工厂模式,作为一种强大的创建型设计模式,在处理创建一系列或相关依赖对象的场景时,展现出了独特的优势和灵活性。它通过提供一个创建对象的接口,让开发者能够在不指定…

观察者模式详解:用 Qt 信号与槽机制深入理解

引言 你是否曾遇到这样的需求&#xff1a;一个对象的状态发生变化后&#xff0c;希望通知其他对象进行相应的更新&#xff1f;比如&#xff1a; 新闻订阅系统&#xff1a;当新闻发布后&#xff0c;所有订阅者都会收到通知。股票行情推送&#xff1a;股价变化时&#xff0c;所…

OSWorld:开启多模态智能体的真实计算机环境革命

OSWorld:开启多模态智能体的真实计算机环境革命 在人工智能技术突飞猛进的今天,多模态智能体正逐步突破实验室的限制,试图融入人类的日常工作场景。然而,如何评估这些智能体在真实计算机环境中处理开放式任务的能力,成为学术界和产业界共同关注的难题。2024年,由xlang-ai…

LabVIEW烟气速度场实时监测

本项目针对燃煤电站烟气流速实时监测需求&#xff0c;探讨了静电传感器结构与速度场超分辨率重建方法&#xff0c;结合LabVIEW多板卡同步采集与实时处理技术&#xff0c;开发出一个高效的烟气速度场实时监测系统。该系统能够在高温、高尘的复杂工况下稳定运行&#xff0c;提供高…

强化学习基础篇二:马尔可夫决策过程

写在前面 本文是对李沐等“动手学强化学习”教程的个人阅读总结&#xff0c;原文链接&#xff1a;动手学强化学习。 第3章 马尔可夫决策过程 3.1 重要性 马尔可夫决策过程是强化学习中的基础概念&#xff0c;强化学习中的环境就是一个马尔可夫决策过程&#xff0c;与多臂老虎…

EtherCAT转profinet网关集成汽车变速箱制造生产线自动化升级

客户的汽车零部件制造商需要升级其变速箱齿轮加工生产线&#xff0c;面临的关键挑战是整合新引进的欧洲齿轮精密检测设备&#xff08;基于EtherCAT协议&#xff09;与现有使用profinet协议自动化系统通信。 企业核心控制平台基于西门子PLC&#xff0c;而现场各工位采用分布式I/…

tongweb7控制台无法访问

tongweb7控制台无法访问 排查 1.首先确认版本&#xff0c;如果版本是轻量级版本&#xff0c;轻量版不支持会话(session)的备份和复制、管理控制台、APM 运维工具等企业级增量功能。 2.查看端口 命令&#xff1a;ss -tnlp 或者netstat -tnlp 确认控制台端口是否开启 3.在conf…

【STM32】从新建一个工程开始:STM32 新建工程的详细步骤

STM32 开发通常使用 Keil MDK、STM32CubeMX、IAR 等工具来创建和管理工程。此处是 使用 Keil MDK5 STM32CubeMX 创建 STM32 工程的详细步骤。 新建的标准库工程文件已上传至资源中&#xff0c;下载后即可直接使用。 标准库新建 STM32 工程的基本目录结构&#xff1a;STD_STM…