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

Java集合的底层原理

目录

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 在元素顺序和排序功能上提供了更多的支持,但可能在某些操作上稍微慢一些,尤其是在最坏情况下的性能。

几个问题思考

相关文章:

  • 深入解析ES6+新语法:复杂的迭代器与生成器
  • 【css酷炫效果】纯CSS实现手风琴折叠效果
  • navicat导出文件密码解密
  • vue3二次封装tooltip实现el-table中的show-overflow-tooltip效果
  • 003 SpringCloud整合-LogStash安装及ELK日志收集
  • Spring Boot集成JWT:打造安全的RESTful API
  • Linux上离线安装PyTorch教程:No module named ‘_bz2:No module named ‘_lzma‘
  • 单元测试mock
  • 蓝桥杯备考:特殊01背包问题——》集合subset
  • 两款软件助力图片视频去水印及图像编辑
  • PHP转GO Go语言环境搭建(Day1) 常见问题及解决方案指南
  • Node.js系列(3)--集群部署指南
  • K8S-etcd服务无法启动问题排查
  • Android audio(8)-native音频服务的启动与协作(audiopolicyservice和audioflinger)
  • 网络华为HCIA+HCIP VLAN间通信
  • ubuntu下TFTP服务器搭建
  • [GHCTF 2025]Goph3rrr [127.0.0.1绕过][env命令查找flag]
  • 如何让焦虑为城市供能 | 杂谈
  • windows上LISTENER监听器中显示“监听程序不支持服务”
  • hackmyvm-Smol
  • 公募基金行业迎系统性变革:基金公司业绩差必须少收费
  • 涉个人信息收集使用问题,15款App和16款SDK被通报
  • 蓝佛安:中方将采取更加积极有为的宏观政策,有信心实现2025年的5%左右增长目标
  • 经济日报头版刊文:为什么贸易战没有出路
  • 山大齐鲁医院回应护士论文现“男性确诊子宫肌瘤”:给予该护士记过处分、降级处理
  • 长三角铁路今日预计发送旅客398万人次,客流持续保持高位运行