养殖企业网站模板,wordpress加密授权,网站建设属于经营什么范围,网站建设与管理t7372一、Set 集合基础#xff1a;核心特点#xff08;入门#xff09;Set 是 Java 集合框架#xff08;Collection#xff09;的子接口#xff0c;它的核心定位是存储不重复的元素#xff0c;这是和 List 最核心的区别。1. 核心特点特性说明元素不可重复集合中不会存在两个 …一、Set 集合基础核心特点入门Set 是 Java 集合框架Collection的子接口它的核心定位是存储不重复的元素这是和 List 最核心的区别。1. 核心特点特性说明元素不可重复集合中不会存在两个 “相等” 的元素“相等” 的判断规则是 Set 实现类的核心无序部分实现例外普通 Set如 HashSet不保证元素的存储 / 遍历顺序TreeSet 是有序的无索引不能通过下标index访问元素只能通过迭代器、增强 for 循环遍历允许存储 nullHashSet 允许存 1 个 nullTreeSet 不允许存 null2. 常用子实现类日常开发中Set 最常用的三个实现类是HashSet最常用基于哈希表实现无序、查询效率高LinkedHashSetHashSet 的子类基于哈希表 链表保证元素的插入顺序TreeSet基于红黑树实现能对元素进行自然排序或自定义排序二、Set 核心实现类的底层原理进阶1. HashSet最核心1底层结构JDK 8 及以后HashSet 的底层是HashMap之前是 HashMap 链表更具体地说HashSet 内部维护一个 HashMap 对象把自己要存储的元素作为 HashMap 的keyHashMap 的 value 是一个固定的空对象PRESENT仅作为占位符2去重原理核心HashSet 判断元素是否重复依赖两个方法hashCode()先计算元素的哈希值确定元素在哈希表中的 “桶位置”equals()如果哈希值相同再通过 equals 方法判断元素是否真的相等graph TD A[添加元素e] -- B[计算e的hashCode()] B -- C{哈希值是否已存在?} C --|否| D[直接存入哈希表] C --|是| E[调用equals()比较已有元素和e] E --|相等| F[不存入去重成功] E --|不相等| G[存入哈希冲突用链表/红黑树存储]2. LinkedHashSet底层是LinkedHashMapHashMap 的子类相比 HashSet多了一个双向链表维护元素的插入顺序因此遍历顺序和插入顺序一致去重原理和 HashSet 完全相同只是多了顺序保证性能略低于 HashSet3. TreeSet底层是TreeMap基于红黑树一种自平衡的二叉查找树实现核心特点有序自然排序如数字升序、字符串字典序或自定义排序去重原理不依赖 hashCode 和 equals而是依赖比较规则Comparable/Comparator如果元素实现了Comparable接口调用compareTo()方法返回 0 则认为元素重复如果自定义了Comparator比较器调用compare()方法返回 0 则认为元素重复注意TreeSet 存储的元素必须是 “可比较的”否则会抛出ClassCastException三、自定义对象去重实战重点日常开发中我们经常需要存储自定义对象如 User、Student到 Set 中并实现去重这是 Set 最核心的实战场景分两种情况讲解场景 1HashSet/LinkedHashSet 存储自定义对象去重核心要求必须重写自定义对象的hashCode()和equals()方法。示例Student 类按学号去重import java.util.HashSet; import java.util.Objects; // 自定义学生类 class Student { private String id; // 学号唯一标识 private String name; private int age; // 构造方法 public Student(String id, String name, int age) { this.id id; this.name name; this.age age; } // 重写equals只要学号相同就认为是同一个学生 Override public boolean equals(Object o) { if (this o) return true; // 引用相同直接相等 if (o null || getClass() ! o.getClass()) return false; // 类型不同不相等 Student student (Student) o; return Objects.equals(id, student.id); // 仅比较学号 } // 重写hashCode基于学号生成哈希值保证equals相等的对象hashCode一定相等 Override public int hashCode() { return Objects.hash(id); } // 重写toString方便打印查看 Override public String toString() { return Student{id id , name name , age age }; } } // 测试类 public class HashSetCustomObjectTest { public static void main(String[] args) { HashSetStudent studentSet new HashSet(); // 添加3个学生其中两个学号相同应该去重 studentSet.add(new Student(001, 张三, 20)); studentSet.add(new Student(002, 李四, 21)); studentSet.add(new Student(001, 张三三, 20)); // 学号和第一个相同应被去重 // 遍历输出只会打印2个学生001和002 for (Student s : studentSet) { System.out.println(s); } } }关键说明重写equals的规则你认为哪些字段相同就代表对象重复就比较哪些字段示例中只比较学号。重写hashCode的原则equals 相等的对象hashCode 必须相等示例中都基于 id 生成保证这一点hashCode 相等的对象equals 不一定相等哈希冲突。IDE如 IDEA可以自动生成 equals 和 hashCode建议直接用自动生成的快捷键Alt Insert。场景 2TreeSet 存储自定义对象去重有两种实现方式实现 Comparable 接口推荐、自定义 Comparator 比较器。方式 1实现 Comparable 接口import java.util.TreeSet; // 学生类实现Comparable接口指定比较规则 class Student implements ComparableStudent { private String id; private String name; private int age; public Student(String id, String name, int age) { this.id id; this.name name; this.age age; } // 重写compareTo按学号比较返回0则去重 Override public int compareTo(Student o) { // 按学号字典序比较 return this.id.compareTo(o.id); // 如果想多字段比较先按学号再按年龄 // int idCompare this.id.compareTo(o.id); // return idCompare ! 0 ? idCompare : Integer.compare(this.age, o.age); } Override public String toString() { return Student{id id , name name , age age }; } } // 测试类 public class TreeSetCustomObjectTest1 { public static void main(String[] args) { TreeSetStudent studentSet new TreeSet(); studentSet.add(new Student(001, 张三, 20)); studentSet.add(new Student(002, 李四, 21)); studentSet.add(new Student(001, 张三三, 20)); // 学号相同被去重 // 遍历输出有序按学号字典序且只有2个元素 for (Student s : studentSet) { System.out.println(s); } } }方式 2自定义 Comparator 比较器灵活不修改原类import java.util.Comparator; import java.util.TreeSet; // 学生类不实现Comparable class Student { private String id; private String name; private int age; public Student(String id, String name, int age) { this.id id; this.name name; this.age age; } Override public String toString() { return Student{id id , name name , age age }; } // getter方法比较器需要获取字段 public String getId() { return id; } } // 测试类用Comparator自定义比较规则 public class TreeSetCustomObjectTest2 { public static void main(String[] args) { // 创建TreeSet时传入Comparator指定按学号比较 TreeSetStudent studentSet new TreeSet(new ComparatorStudent() { Override public int compare(Student s1, Student s2) { return s1.getId().compareTo(s2.getId()); } }); studentSet.add(new Student(001, 张三, 20)); studentSet.add(new Student(002, 李四, 21)); studentSet.add(new Student(001, 张三三, 20)); // 去重 for (Student s : studentSet) { System.out.println(s); } } }四、进阶注意点HashSet 性能增删改查的平均时间复杂度是 O (1)哈希冲突严重时会退化为 O (n)链表或 O (logn)红黑树。尽量让 hashCode 分布均匀减少哈希冲突IDE 自动生成的 hashCode 已优化。TreeSet 性能增删改查的时间复杂度是 O (logn)比 HashSet 略慢但胜在有序。不可变对象存储到 Set 中的对象如果是可变的比如修改了用于比较的字段可能导致去重失效。示例把 Student 对象存入 HashSet 后修改其 id 字段会导致后续无法正确识别该对象甚至出现重复元素。总结核心特点Set 集合的核心是元素不重复无索引HashSet 无序、LinkedHashSet 保证插入顺序、TreeSet 保证排序顺序。去重原理HashSet/LinkedHashSet 依赖hashCode()equals()TreeSet 依赖Comparable/Comparator的比较规则返回 0 则去重。自定义对象去重用 HashSet 需重写hashCode()和equals()用 TreeSet 需实现Comparable或传入Comparator且比较规则要和 “去重逻辑” 一致。HashSet的底层结构一、HashSet 底层的 “本质”基于 HashMap 封装首先要明确一个核心结论HashSet 本身没有独立的底层结构它是对 HashMap 的 “轻量封装”。HashSet 内部会维护一个HashMap类型的成员变量源码中叫map。当你向 HashSet 中添加元素时实际上是把这个元素作为HashMap 的 key存入而 HashMap 的 value 是一个固定的、静态的空对象PRESENT类型为Object仅作为占位符使用因为 HashMap 必须有 key-value 对而 HashSet 只需要 key。源码佐证JDK 8public class HashSetE extends AbstractSetE implements SetE, Cloneable, java.io.Serializable { // 核心HashSet 内部的 HashMap private transient HashMapE,Object map; // 固定的占位符 value private static final Object PRESENT new Object(); // HashSet 的无参构造器 → 本质是创建一个空的 HashMap public HashSet() { map new HashMap(); } // HashSet 的 add 方法 → 本质是调用 HashMap 的 put 方法 public boolean add(E e) { return map.put(e, PRESENT)null; } // HashSet 的 remove 方法 → 本质是调用 HashMap 的 remove 方法 public boolean remove(Object o) { return map.remove(o)PRESENT; } }从源码能直接看出HashSet 的所有核心操作add/remove/contains 等都是通过调用 HashMap 的对应方法实现的它只是一个 “只使用 key 的 HashMap 包装器”。总结核心本质HashSet 没有独立底层结构是对 HashMap 的封装元素存储为 HashMap 的 keyvalue 是固定空对象。底层依赖HashMap 的 “数组 链表 红黑树” 结构数组是主容器链表解决哈希冲突红黑树优化高频冲突场景。存储流程计算元素哈希值→确定桶位置→通过 hashCode ()equals () 判重→存入 / 扩容这也是 HashSet 去重的底层逻辑。补充HashSet 中 “元素唯一性”一、先拆解两个 “相等” 分别对应什么1. 哈希值相等是 “数字相等”底层标识的初步匹配hashCode()方法的返回值是一个int类型的数字比如 12345、67890“哈希值相等” 就是这两个数字完全一样。这个数字的本质是对象的 “哈希标识”HashMap/HashSet 用它快速定位元素该放在哪个 “桶” 里类似快递的分区编号。举例子字符串s1 Javas1.hashCode()返回 2301506字符串s2 new String(Java)s2.hashCode()也返回 2301506这就叫 “哈希值相等”—— 两个对象的hashCode()返回的数字一样。如果是未重写hashCode()的自定义对象new Student(001)和new Student(001)的hashCode()返回的是两个不同的数字因为默认基于内存地址生成这就是 “哈希值不相等”。2. 内容相等是 “业务逻辑上的相等”你定义的相等规则equals()方法返回true代表你认为这两个对象 “在业务意义上是同一个”而不是 “内存地址相同”Object 类默认是比较内存地址但我们重写后就是比较业务字段。这个 “内容” 的本质是你自定义的规则比如 “学号相同就是同一个学生”“手机号相同就是同一个用户”。举例子对于重写了equals()的 Student 类s1 new Student(001, 张三)s2 new Student(001, 张三三)因为equals()只比较学号所以s1.equals(s2)返回true业务上相等对于未重写equals()的 Student 类即使两个对象学号、姓名都一样equals()也返回false因为默认比较内存地址两个对象是不同的内存地址对于 String 类Java.equals(new String(Java))返回trueString 已经重写了equals()比较的是字符串内容。二、两个条件的关系是 “先筛后验”必须同时满足才判定为 “重复元素”HashSet 判断 “两个元素是否重复”是先通过哈希值快速筛再通过 equals 精准验只有两步都满足 “相等”才会认为是重复元素。补充LinkedHashSet 基础一、LinkedHashSet 基础核心特点入门LinkedHashSet 是 HashSet 的直接子类因此它完全继承了 HashSet 的核心特性同时又新增了自己的核心能力 ——保证元素的插入顺序。1. 核心特点对比 HashSet 记忆特性LinkedHashSetHashSet元素唯一性✅ 支持和 HashSet 判重规则完全一致✅ 支持顺序性✅ 保证插入顺序遍历顺序 插入顺序❌ 无序遍历顺序随机底层依赖LinkedHashMapHashMap允许存储 null✅ 允许 1 个 null✅ 允许 1 个 null性能略低于 HashSet维护链表开销更高无额外顺序维护开销线程安全性❌ 非线程安全❌ 非线程安全2. 直观示例验证 “插入顺序”先通过一个简单例子感受 LinkedHashSet 和 HashSet 的核心差异import java.util.HashSet; import java.util.LinkedHashSet; public class LinkedHashSetBasicTest { public static void main(String[] args) { // 1. HashSet无序 HashSetString hashSet new HashSet(); hashSet.add(苹果); hashSet.add(香蕉); hashSet.add(橙子); System.out.println(HashSet遍历结果 hashSet); // 输出顺序随机比如 [橙子, 苹果, 香蕉] // 2. LinkedHashSet按插入顺序遍历 LinkedHashSetString linkedHashSet new LinkedHashSet(); linkedHashSet.add(苹果); linkedHashSet.add(香蕉); linkedHashSet.add(橙子); System.out.println(LinkedHashSet遍历结果 linkedHashSet); // 输出固定[苹果, 香蕉, 橙子] } }从示例能直接看出LinkedHashSet 遍历出来的元素顺序和你添加的顺序完全一致这是它和 HashSet 最直观的区别。二、LinkedHashSet 底层原理进阶因为 LinkedHashSet 是 HashSet 的子类所以它的底层原理和 HashSet 高度相似核心差异在于 “底层依赖的 Map 不同”——HashSet 依赖 HashMapLinkedHashSet 依赖 LinkedHashMap。1. 核心结论LinkedHashSet 是 LinkedHashMap 的 “包装器”和 HashSet 封装 HashMap 一样LinkedHashSet 内部也维护一个LinkedHashMap对象所有核心操作add/remove/contains都是调用 LinkedHashMap 的方法实现的存储元素时把元素作为 LinkedHashMap 的keyvalue 是固定的空对象PRESENT和 HashSet 完全一样判重规则和 HashSet 完全一致hashCode()equals()自定义对象去重仍需重写这两个方法唯一差异LinkedHashMap 比 HashMap 多了一个双向链表用于维护元素的插入顺序。2. 源码佐证JDK 8public class LinkedHashSetE extends HashSetE implements SetE, Cloneable, java.io.Serializable { // 无参构造器调用父类 HashSet 的构造器创建一个 LinkedHashMap public LinkedHashSet() { super(16, 0.75f, true); // 第三个参数 true 代表“访问顺序”不是“是否按插入顺序” } // 父类 HashSet 的这个构造器仅给 LinkedHashSet 调用 HashSet(int initialCapacity, float loadFactor, boolean dummy) { map new LinkedHashMap(initialCapacity, loadFactor); // 核心创建 LinkedHashMap } // add 方法直接继承自 HashSet本质是调用 LinkedHashMap 的 put 方法 // public boolean add(E e) { return map.put(e, PRESENT)null; } }从源码能看出LinkedHashSet 没有重写任何核心方法只是在构造时把底层的 map 从 HashMap 换成了 LinkedHashMap。3. LinkedHashMap 的底层结构LinkedHashSet 顺序的核心LinkedHashMap 是 HashMap 的子类它的底层结构是“数组 链表 红黑树 双向链表”基础结构数组 链表 红黑树和 HashMap 完全一致用于存储元素、解决哈希冲突新增结构双向链表额外维护一个 “头节点→元素节点→尾节点” 的双向链表每个节点除了保存哈希表的信息还保存了before前一个节点和after后一个节点指针用于记录元素的插入顺序。4. 可视化理解LinkedHashSet 底层结构LinkedHashSet (外部表现) ↓ 内部依赖 LinkedHashMap ├── table (桶数组和 HashMap 一样) │ ├── 下标0: null │ ├── 下标1: Node(苹果, PRESENT) → null │ ├── 下标2: Node(香蕉, PRESENT) → null │ └── ... ├── 双向链表维护插入顺序 │ 头节点 → Node(苹果) → Node(香蕉) → Node(橙子) → 尾节点 ├── size (元素个数) └── PRESENT (固定value)哈希表数组 链表 红黑树负责快速查找、判重和 HashSet 一样双向链表负责维护顺序遍历的时候直接遍历这个双向链表因此能保证插入顺序。5. LinkedHashSet 存储元素的流程对比 HashSet以linkedHashSet.add(苹果)为例流程和 HashSet 几乎一致只多了 “维护双向链表” 的步骤计算 “苹果” 的哈希值确定桶位置通过hashCode()equals()判断是否重复重复则不存入不重复则把元素存入哈希表的对应桶中把元素节点添加到双向链表的尾部保证插入顺序。三、LinkedHashSet 自定义对象去重实战因为 LinkedHashSet 的判重规则和 HashSet 完全一致所以自定义对象去重的方式也完全相同 —— 必须同时重写hashCode()和equals()且逻辑要一致。