红黑树、HashSet、LinkedHashSet底层原理

1. 数据结构(红黑树)

  • 红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。
  • 1972年出现,当时被称之为平衡二叉B树。后来,1978年被修改为如今的”红黑树"。
  • 它是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色。
  • 每一个节点可以是红或者黑;红黑树不是高度平衡的,它的平衡是通过“红黑规则”进行实现的。

平衡二叉树: 

        高度平衡。

        当左右子树高度差超过1时,通过旋转保持平衡。

红黑树:

        是一个二叉查找树:
                但是不是高度平衡的。
                条件:特有的红黑规则。

1.1 数据结构(红黑树)红黑规则

        每一个节点或是红色的,或者是黑色的。

        根节点必须是黑色。

        如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的。

如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)。

        对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

1.2 数据结构(红黑树)添加节点的规则

默认颜色:添加节点默认是红色的(效率高)

 1.3 数据结构(红黑树)添加节点的规则

        红黑树在添加节点的时候,添加的节点默认是红色的。

2. HashSet

2.1 存储字符串并遍历

利用Set系列的集合,添加字符串,并使用多种方式遍历。

        迭代器

        增强for

        Lambda表达式

import java.util.HashSet;
import java.util.Set;

public class A01_SetDemo1 {
    public static void main(String[] args) {
       /*
           利用Set系列的集合,添加字符串,并使用多种方式遍历。
            迭代器
            增强for
            Lambda表达式

        */


        //1.创建一个Set集合的对象
        Set<String> s = new HashSet<>();

        //2,添加元素
        //如果当前元素是第一次添加,那么可以添加成功,返回true
        //如果当前元素是第二次添加,那么添加失败,返回false
        s.add("张三");
        s.add("张三");
        s.add("李四");
        s.add("王五");

        //3.打印集合
        //无序
        //System.out.println(s);//[李四, 张三, 王五]

        //迭代器遍历
       /* Iterator<String> it = s.iterator();
        while (it.hasNext()){
            String str = it.next();
            System.out.println(str);
        }
*/

        //增强for
       /* for (String str : s) {
            System.out.println(str);
        }*/

        // Lambda表达式
        s.forEach( str->System.out.println(str));


    }
}

2.2 HashSet底层原理

  •  HashSet集合底层采取哈希表存储数据
  •  哈希表是一种对于增删改查数据性能都较好的结构

2.3 哈希表组成

  • JDK8之前:数组+链表
  • JDK8开始:数组+链表+红黑树

2.4 哈希值

哈希值:对象的整数表现形式

  •  根据hashCode方法算出来的int类型的整数
  • 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
  • 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值

2.5 对象的哈希值特点

  • 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
  • 如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
  • 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
public class A02_HashSetDemo1 {
    public static void main(String[] args) {
        /*
            哈希值:
                对象的整数表现形式
                1. 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
                2. 如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
                3. 但是在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)

         */

        //1.创建对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("zhangsan",23);

        //2.如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
        //  如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
        System.out.println(s1.hashCode());//-1461067292
        System.out.println(s2.hashCode());//-1461067292


        //在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。
        //哈希碰撞
        System.out.println("abc".hashCode());//96354
        System.out.println("acD".hashCode());//96354

    }
}

2.6 HashSetJDK8以前底层原理

        创建一个默认长度16,默认加载因为0.75的数组,数组名table

        根据元素的哈希值跟数组的长度计算出应存入的位置

        判断当前位置是否为null,如果是null直接存入

        如果位置不为null,表示有元素,则调用equals方法比较属性值

        一样:不存           不一样:存入数组,形成链表

                JDK8以前:新元素存入数组,老元素挂在新元素下面

                JDK8以后:新元素直接挂在老元素下面

        int index=(数组长度-1)& 哈希值

2.7 HashSet底层原理

        JDK8以后,当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树

        如果集合中存储的是自定义对象,必须要重hashCode和equals方法

利用HashSet集合去除重复元素

        需求:创建一个存储学生对象的集合,存储多个学生对象。

        使用程序实现在控制台遍历该集合。

        要求:学生对象的成员变量值相同,我们就认为是同一个对象。

import java.util.HashSet;
public class A03_HashSetDemo2 {
    public static void main(String[] args) {
        /* 需求:创建一个存储学生对象的集合,存储多个学生对象。
            使用程序实现在控制台遍历该集合。
            要求:学生对象的成员变量值相同,我们就认为是同一个对象


        String   Integer
*/
        //1.创建三个学生对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("lisi",24);
        Student s3 = new Student("wangwu",25);
        Student s4 = new Student("zhangsan",23);


        //2.创建集合用来添加学生
        HashSet<Student> hs = new HashSet<>();

        //3.添加元素
        System.out.println(hs.add(s1));
        System.out.println(hs.add(s2));
        System.out.println(hs.add(s3));
        System.out.println(hs.add(s4));

        //4.打印集合
        System.out.println(hs);
    }
}
import java.util.HashSet;
public class A03_HashSetDemo2 {
    public static void main(String[] args) {
        /* 需求:创建一个存储学生对象的集合,存储多个学生对象。
            使用程序实现在控制台遍历该集合。
            要求:学生对象的成员变量值相同,我们就认为是同一个对象


        String   Integer
*/
        //1.创建三个学生对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("lisi",24);
        Student s3 = new Student("wangwu",25);
        Student s4 = new Student("zhangsan",23);


        //2.创建集合用来添加学生
        HashSet<Student> hs = new HashSet<>();

        //3.添加元素
        System.out.println(hs.add(s1));
        System.out.println(hs.add(s2));
        System.out.println(hs.add(s3));
        System.out.println(hs.add(s4));

        //4.打印集合
        System.out.println(hs);
    }
}

3. LinkedHashSet底层原理

        有序、不重复、无索引。

        这里的有序指的是保证存储和取出的元素顺序一致。

        原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。

 

import java.util.LinkedHashSet;
public class A04_LinkedHashSetDemo {
    public static void main(String[] args) {
        //1.创建4个学生对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("lisi",24);
        Student s3 = new Student("wangwu",25);
        Student s4 = new Student("zhangsan",23);


        //2.创建集合的对象
        LinkedHashSet<Student> lhs = new LinkedHashSet<>();


        //3.添加元素
        System.out.println(lhs.add(s3));
        System.out.println(lhs.add(s2));
        System.out.println(lhs.add(s1));
        System.out.println(lhs.add(s4));

        //4.打印集合
        System.out.println(lhs);
    }
}

<think>我们被要求回答HashSetLinkedHashSet和TreeSet的区别。根据之前的引用内容,我们可以从以下几个方面进行对比: 1. 底层实现 2. 元素唯一性 3. 元素排序/顺序特性 4. 性能比较(插入、删除、查找) 5. 适用场景 此外,我们还需要注意回答的结构,使用适当的标题,并在最后生成相关问题。 根据引用[1][2][3][4]的内容,我们可以总结如下: 1. 底层实现: - HashSet: 基于HashMap实现,使用哈希表(数组+链表/红黑树)。 - LinkedHashSet: 继承自HashSet底层使用LinkedHashMap,维护了一个双向链表来记录插入顺序。 - TreeSet: 基于TreeMap实现,使用红黑树(自平衡二叉查找树)。 2. 元素唯一性:三者都实现了Set接口,因此都不允许重复元素。 3. 元素排序/顺序特性: - HashSet:无序,不保证元素的任何顺序(迭代顺序可能变化)。 - LinkedHashSet:按照元素插入的顺序进行排序(即插入顺序)。 - TreeSet:按照元素的自然顺序(实现Comparable接口)或者根据提供的Comparator进行排序(树的有序结构保证)。 4. 性能比较: - HashSet:插入、删除和查找操作的时间复杂度通常为$O(1)$(在哈希函数分散均匀的情况下)。 - LinkedHashSet:由于需要维护双向链表,性能略低于HashSet,但迭代顺序可预测,且迭代性能比HashSet好(因为链表连接了所有元素)。 - TreeSet:插入、删除和查找操作的时间复杂度为$O(\log n)$,因为需要维护红黑树的平衡。 5. 允许null值: - HashSetLinkedHashSet允许一个null元素。 - TreeSet不允许null元素(因为需要比较,null无法比较)。 6. 线程安全:三者都不是线程安全的。 7. 适用场景: - HashSet:当不需要维护顺序,只需要高效地添加、删除和判断元素是否存在时使用。 - LinkedHashSet:当需要维护元素的插入顺序时使用。 - TreeSet:当需要元素按照排序顺序(升序或降序)时使用。 我们按照以上要点组织答案。 </think>### HashSetLinkedHashSet与TreeSet的核心区别 以下是Java中三种Set实现的对比总结,涵盖底层实现、排序特性、性能表现和适用场景: | **特性** | **HashSet** | **LinkedHashSet** | **TreeSet** | |------------------|--------------------------------|--------------------------------------|--------------------------------| | **底层实现** | 基于`HashMap`(哈希表+链表/红黑树) | 继承`HashSet`,底层为`LinkedHashMap`(哈希表+双向链表) | 基于`TreeMap`(红黑树) | | **元素唯一性** | ✅ 不允许重复元素 | ✅ 不允许重复元素 | ✅ 不允许重复元素 | | **排序特性** | ❌ 完全无序 | ✅ 保留插入顺序 | ✅ 自然排序或自定义排序(`Comparator`) | | **`null`支持** | ✅ 允许1个`null`元素 | ✅ 允许1个`null`元素 | ❌ 禁止`null`(需可比较) | | **时间复杂度** | $O(1)$(理想情况) | $O(1)$(操作略慢于`HashSet`) | $O(\log n)$(所有操作) | | **内存开销** | 较低(仅哈希表) | 较高(额外维护链表指针) | 较高(红黑树结构) | | **线程安全** | ❌ 非线程安全 | ❌ 非线程安全 | ❌ 非线程安全 | #### 详细解析 1. **底层实现与排序差异** - **HashSet**:通过`hashCode()`定位存储桶位置,迭代顺序不可预测(如`[A,C,B]`)。 - **LinkedHashSet**:在`HashSet`基础上增加双向链表,严格保持插入顺序(如`[A,B,C]`)[^1][^4]。 - **TreeSet**:通过红黑树实现自动排序(默认自然顺序,如字母升序`[A,B,C]`),可自定义`Comparator`[^3]。 2. **性能对比** - **插入/查找效率**: - `HashSet` ≈ `LinkedHashSet` > `TreeSet` - 原因:`HashSet`和`LinkedHashSet`基于哈希表(常数时间),`TreeSet`需维护红黑树平衡(对数时间)[^3]。 - **迭代效率**: - `LinkedHashSet` > `TreeSet` > `HashSet` - 原因:`LinkedHashSet`链表遍历直接有序,`TreeSet`中序遍历有序,`HashSet`需扫描全部哈希桶[^2]。 3. **适用场景建议** - **HashSet**:高频读写场景,不关心顺序(如去重统计)。 ```java Set<String> cache = new HashSet<>(); // 网页URL去重缓存 ``` - **LinkedHashSet**:需保留操作历史的场景(如用户操作记录)。 ```java Set<String> userActions = new LinkedHashSet<>(); // 记录用户点击顺序 ``` - **TreeSet**:需动态排序的场景(如排行榜)。 ```java Set<Integer> highScores = new TreeSet<>(Comparator.reverseOrder()); // 游戏分数降序 ``` #### 关键注意事项 - **哈希碰撞影响**:`HashSet`和`LinkedHashSet`性能受`hashCode()`质量影响,碰撞过多会导致退化为$O(n)$[^3]。 - **线程安全方案**:三者均非线程安全,推荐: ```java Set<String> safeSet = Collections.synchronizedSet(new HashSet<>()); // 同步包装 Set<String> concurrentSet = new ConcurrentSkipListSet<>(); // 并发有序集合 ``` - **扩展选择**: - 需线程安全+排序 → `ConcurrentSkipListSet` - 需高并发+无序 → `CopyOnWriteArraySet`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值