HashSet/LinkedHashSet/TreeSet 原理解析

HashSet/LinkedHashSet/TreeSet 原理解析

第一章:Set 整体架构(前置基础,必记)

1.1 Set 接口核心特点

核心解析:Set接口继承自Collection接口,是Java集合体系中用于存储"不可重复元素"的核心接口,其核心特点的设计的逻辑,完全为"去重"和"高效访问"服务,与List接口(有序、可重复、有索引)形成鲜明对比。

核心特点(自用重点,必记):

  1. 不可重复:这是Set最核心的特性,无论哪种实现类,都不允许存储重复元素,去重逻辑依托底层Map的Key特性;

  2. 无索引:不提供通过下标访问元素的方法(如get(int index)),无法直接定位元素,访问方式依赖迭代或contains()判断;

  3. 有序性差异:并非所有Set都无序,HashSet无序、LinkedHashSet保持插入顺序、TreeSet保持自然排序(或自定义排序);

  4. 允许null:除TreeSet外,HashSet、LinkedHashSet均允许存储1个null元素(TreeSet因排序需求,无法存储null)。

面试提醒:常考"Set接口与List接口的区别",核心回答围绕"不可重复vs可重复、无索引vs有索引、有序性差异"展开,无需深入其他细节。

1.2 Set 三大实现类定位(面试高频)

核心解析:Set接口的三大常用实现类,底层均依赖对应的Map实现,本质是"复用Map的Key去重特性",仅封装了Set的对外接口,未重新实现核心逻辑,这是Set原理的核心,记牢这一点可快速掌握三者的底层逻辑。

三大实现类核心定位(联动记忆,必记):

  1. HashSet:最常用的Set实现类,底层基于HashMap,追求高效访问,无有序性要求;

  2. LinkedHashSet:继承自HashSet,底层基于LinkedHashMap,在高效访问的基础上,额外保证"插入顺序";

  3. TreeSet:底层基于TreeMap,核心特性是"自动排序",牺牲部分效率,换取有序性。

自用重点+联动记忆:① 记牢"Set底层都是Map",所有Set的核心逻辑(去重、存储)均复用对应Map的逻辑,无需单独记忆;② 联动前文:HashMap对应HashSet、LinkedHashMap对应LinkedHashSet、TreeMap对应TreeSet,一一对应,降低记忆成本。

1.3 Set 底层核心共性(必记真理)

核心解析:所有Set实现类的底层,均采用"Map的Key存储元素",Value统一存储一个静态空对象(PRESENT),该对象无实际意义,仅用于填充Map的Value位置,因为Map的Key天然不可重复,恰好契合Set"不可重复"的核心需求。

简化源码(核心逻辑,无需深入完整源码):

java 复制代码
// 所有Set底层共用的静态空对象,用于填充Map的Value
private static final Object PRESENT = new Object();
// 底层Map对象,所有Set的核心操作都委托给该Map
private transient HashMap<E, Object> map; // HashSet示例,其他Set类似

自用重点:记牢"Set = Map的Key + 固定空Value",后续解析三者原理时,均围绕这一核心展开,无需单独理解Set的去重、存储逻辑,只需关联对应Map的特性即可。

第二章:HashSet 原理解析(面试重点,最常用)

2.1 底层数据结构(必记)

核心解析:HashSet的底层完全依赖HashMap实现,无额外新增逻辑,其存储结构、高效访问的原理,与HashMap完全一致,可直接联动前文HashMap的底层结构记忆,无需单独推导。

核心结构(联动记忆):

  1. 底层对象:private transient HashMap<E, Object> map;,所有HashSet的操作(add、remove、contains),均委托给该map对象执行;

  2. 存储逻辑:HashSet中存储的元素,全部作为map的Key,每个Key对应的Value,都是同一个静态空对象(PRESENT);

  3. 底层结构:与HashMap一致,采用"数组(哈希表)+ 链表/红黑树"的结构,当链表长度≥8且数组长度≥64时,链表转为红黑树,优化查询效率。

自用重点:HashSet的底层 = HashMap,两者的底层结构、哈希冲突解决方式、高效访问原理完全一致,记牢这一点,可减少重复记忆。

2.2 核心构造器源码解析

核心解析:HashSet的构造器,本质是创建对应的HashMap对象,构造器的参数的逻辑,与HashMap的构造器完全一致,仅封装了一层Set的接口,简化源码如下(聚焦核心逻辑,省略冗余代码)。

java 复制代码
// 1. 空参构造器:创建一个空的HashMap,与HashMap空参构造器逻辑一致
public HashSet() {
    map = new HashMap<>();
}

// 2. 指定初始容量构造器:创建指定初始容量的HashMap,避免频繁扩容
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

// 3. 集合参数构造器:将传入集合的元素,作为map的Key,批量添加
public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c); // 本质是调用map.putAll()
}

自用重点+面试提醒:① HashSet的构造器逻辑与HashMap完全一致,初始容量、负载因子(默认0.75)也与HashMap一致;② 面试常考"HashSet的初始容量是多少",核心回答:空参构造器初始容量为16(依托HashMap),与ArrayList初始容量(10)区分开。

2.3 核心方法源码解析(聚焦面试高频)

核心解析:HashSet的所有核心方法,均是对HashMap对应方法的简单封装,无额外逻辑,重点掌握add、remove、contains三个方法,这三个是面试高频考查点,联动HashMap的对应方法记忆即可。

2.3.1 add() 方法(去重核心,面试必问)

核心逻辑:添加元素时,本质是调用HashMap的put()方法,将元素作为Key,PRESENT作为Value,利用HashMap Key不可重复的特性,实现Set的去重。

java 复制代码
public boolean add(E e) {
    // 调用HashMap的put方法,Key为添加的元素,Value为固定空对象
    return map.put(e, PRESENT) == null;
}

关键细节解析:

  1. HashMap的put()方法,若Key不存在,返回null,此时add()方法返回true,表示添加成功;

  2. 若Key已存在(重复元素),put()方法返回原Value(PRESENT),此时add()方法返回false,表示添加失败,实现去重;

  3. 去重的判断标准,与HashMap Key的判断标准完全一致:先比较hashCode(),再比较equals(),两者都相同,才视为重复元素。

2.3.2 contains() 方法(高频)

核心逻辑:判断元素是否存在,本质是调用HashMap的containsKey()方法,判断Key是否存在,因为Set的元素就是HashMap的Key。

java 复制代码
public boolean contains(Object o) {
    return map.containsKey(o);
}
2.3.3 remove() 方法

核心逻辑:删除元素,本质是调用HashMap的remove()方法,删除对应的Key,返回值表示是否删除成功。

java 复制代码
public boolean remove(Object o) {
    return map.remove(o) == PRESENT;
}

自用重点+联动记忆:① 所有HashSet的核心方法,均委托给HashMap执行,逻辑完全一致,无需单独记忆;② 联动前文HashMap的put、containsKey、remove方法,理解其底层逻辑,即可掌握HashSet的对应方法。

2.4 核心特性(面试必记)

  1. 有序性:无序,不保证元素的插入顺序,也不保证自然排序,元素的存储顺序由哈希表的散列位置决定(与HashMap一致);

  2. 去重:依托HashMap Key的不可重复特性,判断标准为hashCode() + equals();

  3. 线程安全:非线程安全,与HashMap一致,并发场景下可能出现数据异常,无同步锁;

  4. null支持:允许存储1个null元素(因为HashMap允许Key为null);

  5. 效率:增删改查效率均为O(1),依托HashMap的哈希表结构,与HashMap效率一致。

2.5 自用重点 + 面试提醒

① 必记核心:HashSet底层 = HashMap,去重原理 = HashMap Key不可重复,判断标准 = hashCode() + equals();

② 面试高频提问1:"HashSet如何保证元素不重复?",核心回答:底层依赖HashMap,添加元素时作为Key,利用HashMap Key不可重复的特性,判断标准是hashCode()和equals()都相同;

③ 面试高频提问2:"HashSet为什么无序?",核心回答:底层是哈希表,元素存储位置由哈希值决定,与插入顺序无关;

④ 易错点:HashSet的无序≠随机,是哈希表的散列顺序,并非完全随机;

⑤ 补充细节:若要保证HashSet的去重逻辑正确,存储的元素必须重写hashCode()和equals()方法,否则会出现去重失效(与HashMap Key的要求一致)。

第三章:LinkedHashSet 原理解析(面试重点,插入有序)

3.1 底层数据结构(必记)

核心解析:LinkedHashSet继承自HashSet,底层并非重新实现,而是依赖LinkedHashMap实现,其核心结构是"哈希表 + 双向链表",既保留了HashSet的高效访问特性,又通过双向链表保证了元素的插入顺序,联动前文LinkedHashMap的结构记忆即可。

核心结构(联动记忆):

  1. 继承关系:public class LinkedHashSet<E> extends HashSet<E>,继承HashSet的所有方法,仅重写了构造器;

  2. 底层对象:底层实际是LinkedHashMap,HashSet中的map属性,在LinkedHashSet中被赋值为LinkedHashMap对象;

  3. 结构特性:哈希表(保证O(1)效率)+ 双向链表(维护插入顺序),双向链表记录元素的插入顺序,迭代时按插入顺序输出。

自用重点:LinkedHashSet = HashSet + LinkedHashMap,底层结构、效率与HashSet一致,仅多了双向链表维护插入顺序,无需单独记忆新的结构逻辑。

3.2 核心构造器源码解析

核心解析:LinkedHashSet没有新增构造器,仅重写了HashSet的构造器,将底层的map对象,从HashMap改为LinkedHashMap,简化源码如下(聚焦核心逻辑)。

java 复制代码
// 空参构造器:创建LinkedHashMap,初始容量16,负载因子0.75
public LinkedHashSet() {
    super(16, .75f, true); // 调用HashSet的带参构造器,第三个参数指定为true,标识底层是LinkedHashMap
}

// 指定初始容量构造器
public LinkedHashSet(int initialCapacity) {
    super(initialCapacity, .75f, true);
}

// 集合参数构造器
public LinkedHashSet(Collection<? extends E> c) {
    super(Math.max((int) (c.size()/.75f) + 1, 16), .75f, true);
    addAll(c);
}

关键细节:HashSet中有一个隐藏的带参构造器(访问权限为包私有),第三个参数"accessOrder"用于指定底层是否为LinkedHashMap,LinkedHashSet调用该构造器时,传入accessOrder=true,确保底层创建的是LinkedHashMap。

自用重点:LinkedHashSet的构造器,本质是创建LinkedHashMap,初始容量、负载因子与HashSet、HashMap一致,无需单独记忆。

3.3 核心特性(与HashSet对比记忆,面试必记)

  1. 有序性:插入有序,通过双向链表维护元素的插入顺序,迭代时按插入顺序输出,这是与HashSet的核心区别;

  2. 去重:与HashSet完全一致,依托LinkedHashMap Key的不可重复特性,判断标准为hashCode() + equals();

  3. 线程安全:非线程安全,与HashSet、LinkedHashMap一致,无同步锁;

  4. null支持:允许存储1个null元素(与LinkedHashMap一致);

  5. 效率:增删改查效率仍为O(1),但略低于HashSet,因为多了双向链表的维护(插入、删除时需修改链表指针)。

3.4 有序性原理(面试高频)

核心解析:LinkedHashSet的有序性,完全依托LinkedHashMap的双向链表实现,与LinkedHashMap的有序性原理一致,无需单独理解。

关键逻辑:

  1. 每个元素在存储时,除了存入哈希表,还会加入双向链表中;

  2. 双向链表按元素的插入顺序,记录每个元素的前驱和后继节点;

  3. 迭代时,不遍历哈希表,而是遍历双向链表,因此能保证按插入顺序输出。

自用重点+联动记忆:联动前文LinkedHashMap的有序性原理,LinkedHashSet的有序性与之一致,都是"双向链表维护插入顺序",无需单独推导。

3.5 自用重点 + 面试提醒

① 必记核心:LinkedHashSet底层 = LinkedHashMap,核心优势是"去重 + 插入有序",效率略低于HashSet;

② 面试高频提问:"HashSet和LinkedHashSet的区别是什么?",核心回答:两者底层分别是HashMap和LinkedHashMap,核心区别是LinkedHashSet保证插入有序,HashSet无序,效率上HashSet略高;

③ 易错点:LinkedHashSet的有序是"插入有序",不是自然排序,与TreeSet的有序性区分开;

④ 适用场景:需要去重,且需要保证元素插入顺序的场景(如日志存储、历史记录)。

第四章:TreeSet 原理解析(面试重点,自动排序)

4.1 底层数据结构(必记)

核心解析:TreeSet的底层依赖TreeMap实现,与HashSet、LinkedHashSet的底层逻辑一致,均是"复用Map的Key特性",但TreeMap的底层结构是红黑树,因此TreeSet的核心特性是"自动排序",联动前文TreeMap的结构记忆即可。

核心结构(联动记忆):

  1. 底层对象:private transient NavigableMap<E, Object> m;,实际是TreeMap对象,TreeSet的所有操作均委托给该对象;

  2. 存储逻辑:TreeSet中的元素作为TreeMap的Key,Value同样是固定空对象(PRESENT);

  3. 底层结构:与TreeMap一致,采用红黑树结构,红黑树的特性是"平衡二叉树",能保证元素的有序性,增删改查效率为O(log n)。

自用重点:TreeSet底层 = TreeMap,排序原理、底层结构与TreeMap完全一致,无需单独记忆新的逻辑,重点掌握排序相关的细节即可。

4.2 核心构造器源码解析

核心解析:TreeSet的构造器,本质是创建TreeMap对象,支持自然排序和自定义排序两种方式,与TreeMap的构造器逻辑完全一致,简化源码如下。

java 复制代码
// 1. 空参构造器:自然排序,元素必须实现Comparable接口
public TreeSet() {
    this(new TreeMap<>());
}

// 2. 自定义排序:传入Comparator比较器,按比较器逻辑排序
public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

// 3. 集合参数构造器:自然排序,将集合元素作为TreeMap的Key
public TreeSet(Collection<? extends E> c) {
    this();
    addAll(c);
}

关键细节解析:

  1. 自然排序:空参构造器创建的TreeSet,默认采用自然排序,要求存储的元素必须实现Comparable接口,重写compareTo()方法,否则会抛出ClassCastException;

  2. 自定义排序:传入Comparator比较器,无需元素实现Comparable接口,按比较器的compare()方法逻辑排序,灵活性更高;

  3. 底层关联:所有构造器最终都是创建TreeMap对象,TreeSet的排序逻辑,完全依赖TreeMap的排序逻辑。

4.3 排序原理(面试必问)

核心解析:TreeSet的自动排序,本质是TreeMap的红黑树排序逻辑,红黑树会根据Key的比较结果,自动调整树的结构,保证所有Key按指定顺序排列,迭代时按排序后的顺序输出。

两种排序方式(必记):

4.3.1 自然排序(默认)
  1. 要求:元素必须实现Comparable接口,重写compareTo(T o)方法;

  2. 排序逻辑:按compareTo()方法的返回值排序,返回负数表示当前元素小于参数元素,返回正数表示大于,返回0表示相等(视为重复元素);

  3. 示例:Integer、String等常用类,已实现Comparable接口,默认按升序排序。

4.3.2 自定义排序
  1. 要求:创建TreeSet时,传入Comparator比较器,重写compare(T o1, T o2)方法;

  2. 排序逻辑:按compare()方法的返回值排序,逻辑与compareTo()一致;

  3. 优势:无需修改元素类的代码,可灵活定义排序规则(如降序、按自定义字段排序)。

自用重点+面试提醒:① 排序的核心是"比较逻辑",无论是自然排序还是自定义排序,都需要明确元素的比较规则;② 面试常考"TreeSet的排序方式有哪些",核心回答:自然排序(Comparable)和自定义排序(Comparator);③ 去重与排序的关联:TreeSet的去重,是通过比较逻辑判断的(compareTo()或compare()返回0,视为重复),无需重写hashCode()和equals()(但建议重写,保证逻辑一致性)。

4.4 核心特性(面试必记)

  1. 有序性:自动排序,默认自然升序,可通过Comparator实现自定义排序,与LinkedHashSet的"插入有序"区分开;

  2. 去重:依托TreeMap的Key比较逻辑,compareTo()或compare()返回0,视为重复元素,无需重写hashCode()和equals();

  3. 线程安全:非线程安全,与TreeMap一致,无同步锁;

  4. null支持:不允许存储null,因为null无法参与比较(会抛出NullPointerException);

  5. 效率:增删改查效率为O(log n),低于HashSet和LinkedHashSet,因为红黑树的插入、删除需要维护树的平衡。

4.5 自用重点 + 面试提醒

① 必记核心:TreeSet底层 = TreeMap,核心优势是"去重 + 自动排序",效率O(log n),不允许null;

② 面试高频提问1:"TreeSet为什么不能存null?",核心回答:TreeSet需要对元素进行排序,null无法参与比较,会抛出空指针异常;

③ 面试高频提问2:"TreeSet的去重原理是什么?",核心回答:依托排序逻辑,通过compareTo()或compare()方法判断,返回0视为重复元素;

④ 易错点:TreeSet的有序是"自动排序",不是插入有序,与LinkedHashSet区分开;元素未实现Comparable接口且未传入比较器,会抛出ClassCastException;

⑤ 适用场景:需要去重,且需要元素自动排序的场景(如排行榜、有序数据集)

第五章:三大Set 核心对比(面试重中之重,必背)

5.1 核心维度对比(自用记忆表格,清晰直观)

对比维度 HashSet LinkedHashSet TreeSet
底层实现 HashMap LinkedHashMap TreeMap
底层结构 数组+链表/红黑树 哈希表+双向链表 红黑树
有序性 无序(散列顺序) 插入有序 自动排序(自然/自定义)
去重原理 hashCode() + equals() hashCode() + equals() compareTo()/compare()
null支持 允许1个 允许1个 不允许
时间复杂度 O(1) O(1)(略低) O(log n)
线程安全
适用场景 仅需去重,追求高效 去重+保持插入顺序 去重+自动排序

5.2 面试高频提问及核心回答(直接背诵,适配面试)

提问1:HashSet、LinkedHashSet、TreeSet 的核心区别是什么?

回答:核心区别在于底层实现和有序性:① HashSet底层HashMap,无序,效率O(1);② LinkedHashSet底层LinkedHashMap,插入有序,效率略低于HashSet;③ TreeSet底层TreeMap,自动排序,效率O(log n),不允许null。

提问2:HashSet 如何保证元素不重复?

回答:底层依赖HashMap,添加元素时作为HashMap的Key,利用HashMap Key不可重复的特性实现去重;判断重复的标准是:先比较hashCode(),再比较equals(),两者都相同视为重复元素。

提问3:TreeSet 和 LinkedHashSet 的有序性有什么区别?

回答:TreeSet是"自动排序",默认自然升序,可通过Comparator自定义排序;LinkedHashSet是"插入有序",仅保证迭代顺序与插入顺序一致,不进行排序。

提问4:为什么 TreeSet 不能存 null,而 HashSet 可以?

回答:TreeSet需要对元素进行排序,null无法参与比较(会抛出空指针异常);HashSet底层是HashMap,HashMap允许Key为null,因此HashSet可以存1个null。

提问5:Set 是线程安全的吗?并发场景下如何处理?

回答:HashSet、LinkedHashSet、TreeSet 都不是线程安全的;并发场景下,可使用 Collections.synchronizedSet() 包装,或使用 CopyOnWriteArraySet(效率更高,适合读多写少场景)。

第六章:面试易错点汇总(自用避坑,必记)

6.1 概念误区纠正(高频易错)

  1. 误区1:HashSet 是随机无序的 → 纠正:HashSet是无序的,但不是随机,是哈希表的散列顺序,由元素的hashCode()决定;

  2. 误区2:LinkedHashSet 是排序的 → 纠正:LinkedHashSet是"插入有序",不是排序,仅保证迭代顺序与插入顺序一致,不进行升序/降序排序;

  3. 误区3:TreeSet 去重需要重写 hashCode() 和 equals() → 纠正:TreeSet的去重依托排序逻辑(compareTo()/compare()),无需重写这两个方法,但建议重写,保证逻辑一致性;

  4. 误区4:所有 Set 都不允许存 null → 纠正:仅 TreeSet 不允许存 null,HashSet、LinkedHashSet 均允许存1个null;

  5. 误区5:LinkedHashSet 效率比 HashSet 高 → 纠正:LinkedHashSet效率略低于HashSet,因为多了双向链表的维护成本;

  6. 误区6:Set 底层是独立实现的 → 纠正:所有 Set 的底层都是 Map,仅使用 Map 的 Key 存储元素,Value 是固定空对象。

6.2 版本差异(JDK 1.7 vs 1.8,聚焦面试高频)

  1. HashSet:1.7与1.8核心逻辑无差异,仅HashMap底层结构优化(1.8链表转红黑树),间接影响HashSet的效率;

  2. LinkedHashSet:1.7与1.8无核心差异,仅LinkedHashMap的源码细节优化,不影响对外特性;

  3. TreeSet:1.7与1.8无核心差异,红黑树的平衡逻辑略有优化,不影响排序和去重特性,无需深入细节。

第七章:总结与自用复习建议

------ 核心知识点串联:Set三大实现类,均依托Map实现(HashSet→HashMap、LinkedHashSet→LinkedHashMap、TreeSet→TreeMap),核心差异在于有序性和底层结构,联动前文Map的知识点,可大幅降低记忆成本;

------ 复习重点:记牢"底层实现+有序性+去重原理+效率"四大核心,重点背诵第五章的对比表格和面试高频提问,无需背诵完整源码;

------ 面试技巧:回答Set相关问题时,先讲底层实现,再讲核心特性(有序性、去重、null支持),最后讲适用场景,逻辑更清晰,更易获得面试官认可;

------ 自用口诀(快速记忆):HashSet(快、无序),LinkedHashSet(快、插入有序),TreeSet(慢、自动排序、不存null),底层全是Map。

附录(快速查阅,自用便捷)

------ 核心属性汇总

  1. 公共属性:private static final Object PRESENT = new Object();(所有Set共用的空Value);

  2. HashSet:private transient HashMap<E, Object> map;(底层HashMap对象);

  3. LinkedHashSet:继承HashSet,底层map为LinkedHashMap对象;

  4. TreeSet:private transient NavigableMap<E, Object> m;(底层TreeMap对象)。

------ 高频方法核心源码简化版(直接背诵,应对源码提问)

java 复制代码
// HashSet add() 核心源码
public boolean add(E e) { return map.put(e, PRESENT) == null; }

// LinkedHashSet 构造器核心源码
public LinkedHashSet() { super(16, .75f, true); }

// TreeSet 排序核心(自然排序)
public TreeSet() { this(new TreeMap<>()); }
相关推荐
苏瞳儿2 小时前
创建后端项目-连接MySql并运行成功
java
菜鸟小九2 小时前
JUC(共享模型之管程、synchronized、wait、park、活跃性、renetrantlock、条件变量)
java·开发语言·juc
kongba0072 小时前
学习COZE编程 / LangGraph 通用工作流项目 提示词模板
java·网络·学习
程序员阿明2 小时前
spring boot3识别PDF图纸
java·spring boot·后端·pdf
blxr_3 小时前
Spring AI自定义Advisor
java·spring
kisloy3 小时前
【反爬虫】极验4 W参数逆向分析
java·javascript·爬虫
-Rane3 小时前
【C++】红黑树
java·开发语言
吃不胖爹3 小时前
Sharding-JDBC只分表不分库
java