关于hashset和hashmap,还有treeset和treemap,四个的关系

TreeSet 和 TreeMap 是 Java 集合框架中两个重要的类,它们的关系非常紧密。简单来说,TreeSet 是基于 TreeMap 实现的

可以将它们的关系理解为:TreeSet 是一个只包含"键"的 TreeMap

下面详细解释一下:

1. 核心实现关系

在 Java 的源代码中,TreeSet内部维护了一个 TreeMap对象(或 NavigableMap对象)作为其核心存储。当您向 TreeSet中添加一个元素时,这个元素实际上被当作 key放入了内部的 TreeMap中,而 value则是一个固定的、无意义的占位对象。

2. 共同特征(因为它们共享同一个底层结构)

  • 有序性 :它们都会对元素(对 TreeMap来说是键)进行自然排序 (元素实现 Comparable接口)或根据构造时传入的 **Comparator**​ 进行排序。遍历时,元素会按照排序后的顺序输出。

  • 基于红黑树 :它们的底层都使用红黑树 数据结构实现。这保证了基本的添加、删除、查找操作的时间复杂度为 O(log n)

  • 非线程安全:它们都不是线程安全的类。

3. 主要区别

特性 TreeSet TreeMap
存储内容 只存储单个元素(作为 key 存储键值对(key-valuepairs)
实现接口 实现 Set接口 实现 Map接口
重复元素 不允许重复元素 不允许重复的 key,但 value可以重复
数据关联 只关心元素本身 通过 key来关联和索引 value

4. 一个简单的类比

您可以把 TreeMap想象成一本字典 ,每个单词(key)后面都有对应的详细解释(value)。

TreeSet就像是这本字典的索引表单词列表 ,它只关心有哪些单词(key),并不包含解释。

代码示例说明关系:

复制代码
// TreeSet 的添加操作,内部近似于:
public boolean add(E e) {
    return this.backingTreeMap.put(e, PRESENT) == null; // PRESENT 是一个固定的 Object 对象
}

// 所以,当您使用 TreeSet 时:
TreeSet<String> set = new TreeSet<>();
set.add("Apple");
// 内部相当于执行了:treeMap.put("Apple", new Object());

总结:

TreeSet在功能上是 TreeMap的一个简化版,它利用 TreeMapkey的有序、不重复特性,来实现一个有序的 Set集合。当您只需要一个不重复且有序的集合时,用 TreeSet;当您需要根据键来维护和查找关联的值时,用 TreeMap

好的,这是一个非常核心的Java集合框架问题。我将从关系用法区别两方面,用清晰的对比为您解答。

一、TreeMap vs HashMap

关系

它们都是Map接口的实现类,用于存储键-值对,且都不允许重复的键。它们是满足不同场景需求的两种不同实现方案。

用法区别(核心在于底层数据结构)
特性 TreeMap HashMap
底层数据结构 红黑树(一种自平衡的二叉查找树) 数组 + 链表 + 红黑树(JDK 1.8后,链表过长会树化)
元素顺序 有序 。根据键的自然顺序 或构造时提供的Comparator进行排序。 无序。不保证插入顺序或任何其他顺序(但LinkedHashMap可以保持插入顺序)。
**性能 O( )**​ 增、删、查的平均时间复杂度为 O(log n) 增、删、查的平均时间复杂度为 O(1)。在哈希冲突严重时可能退化。
Key的要求 键必须实现Comparable接口,或者在构造时提供Comparator 键必须正确实现hashCode()equals()方法。
允许null 不允许 (因为需要比较,null无法比较)。 允许一个null
线程安全 非线程安全。 非线程安全。
内存占用 通常比HashMap占用更多内存,因为需要维护树结构(父、左、右指针等)。 相对较低,但为了减少哈希冲突,需要有负载因子控制,可能存在部分空间未利用。
使用场景选择
  • 使用 TreeMap :你需要一个始终排序 的键值对集合。例如,根据员工ID排序显示员工信息,或者需要频繁进行范围查找(如subMapheadMaptailMap)。

  • 使用 HashMap :你只需要高效的存储和检索,不关心顺序。这是最常用Map实现,在绝大多数场景下都是默认选择。


二、TreeSet vs HashSet

关系

与上面类似,它们都是Set接口的实现类,用于存储不重复的元素TreeSet基于TreeMap实现,HashSet基于HashMap实现 。因此,它们的区别本质上就是其底层Map实现的区别。

用法区别
特性 TreeSet HashSet
底层实现 基于 TreeMap(红黑树) 基于 HashMap(哈希表)
元素顺序 有序 。元素按自然顺序或指定的Comparator排序。 无序。不保证任何顺序。
**性能 O( )**​ 增、删、查的平均时间复杂度为 O(log n) 增、删、查的平均时间复杂度为 O(1)
元素的要求 元素必须实现Comparable接口,或者在构造时提供Comparator 元素必须正确实现hashCode()equals()方法。
允许null元素 不允许(取决于比较器,但默认自然排序不允许)。 允许一个null元素
线程安全 非线程安全。 非线程安全。
内存占用 较高(维护树结构)。 较低。
使用场景选择
  • 使用 TreeSet :你需要一个去重且自动排序的集合。例如,从数据库读取一堆用户ID,并希望它们按顺序排列。

  • 使用 HashSet :你只需要一个高效的、用于去重的集合,不关心顺序。这是最常用Set实现。

总结与记忆口诀

  1. 实现关系

    • TreeSet-> 包装了一个TreeMap(元素作Key)

    • HashSet-> 包装了一个HashMap(元素作Key)

  2. 核心区别口诀

    • TreeXxx(TreeMap/TreeSet)有序 ,基于红黑树 ,需要元素可比较 ,性能 O(log n)

    • HashXxx(HashMap/HashSet)无序 ,基于哈希表 ,需要元素有哈希码 ,性能 O(1)

  3. 默认选择

    • 在大多数不需要排序的场景下,优先选择HashMapHashSet,因为它们的平均性能更好。

    • 只有在明确需要排序功能 时,才选择TreeMapTreeSet

当然可以!我用最直观的代码来演示。

一、代码示例:有序 vs 无序

TreeMap(有序)
复制代码
import java.util.*;

public class OrderDemo {
    public static void main(String[] args) {
        // TreeMap - 自动按键排序(这里是字符串的自然顺序:字母顺序)
        TreeMap<Integer, String> treeMap = new TreeMap<>();
        treeMap.put(3, "Charlie");
        treeMap.put(1, "Alice");
        treeMap.put(4, "David");
        treeMap.put(2, "Bob");
        
        System.out.println("TreeMap 输出(按键排序):");
        for (Map.Entry<Integer, String> entry : treeMap.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        // 输出:
        // 1: Alice
        // 2: Bob
        // 3: Charlie
        // 4: David
        // 注意:无论怎么插入,遍历时都是按数字键1,2,3,4的顺序
        
        // TreeMap特有的有序操作:获取子映射
        System.out.println("\nTreeMap 范围查询(2到3之间的键):");
        SortedMap<Integer, String> subMap = treeMap.subMap(2, 4); // [2, 4)
        System.out.println(subMap); // 输出:{2=Bob, 3=Charlie}
    }
}
HashMap(无序)
复制代码
import java.util.*;

public class OrderDemo {
    public static void main(String[] args) {
        // HashMap - 不保证任何顺序
        HashMap<Integer, String> hashMap = new HashMap<>();
        hashMap.put(3, "Charlie");
        hashMap.put(1, "Alice");
        hashMap.put(4, "David");
        hashMap.put(2, "Bob");
        
        System.out.println("HashMap 输出(不保证顺序,可能与插入顺序不同):");
        for (Map.Entry<Integer, String> entry : hashMap.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        // 可能的输出(每次运行可能不同):
        // 1: Alice
        // 2: Bob
        // 3: Charlie
        // 4: David
        // 或者:
        // 3: Charlie
        // 1: Alice
        // 4: David
        // 2: Bob
    }
}
TreeSet vs HashSet(有序 vs 无序)
复制代码
import java.util.*;

public class SetOrderDemo {
    public static void main(String[] args) {
        // TreeSet - 有序
        TreeSet<String> treeSet = new TreeSet<>();
        treeSet.add("Orange");
        treeSet.add("Apple");
        treeSet.add("Banana");
        treeSet.add("Cherry");
        
        System.out.println("TreeSet(字母顺序排序):");
        for (String fruit : treeSet) {
            System.out.println(fruit);
        }
        // 输出:
        // Apple
        // Banana
        // Cherry
        // Orange
        
        // HashSet - 无序
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("Orange");
        hashSet.add("Apple");
        hashSet.add("Banana");
        hashSet.add("Cherry");
        
        System.out.println("\nHashSet(不保证顺序):");
        for (String fruit : hashSet) {
            System.out.println(fruit);
        }
        // 可能的输出(每次可能不同):
        // Orange
        // Apple
        // Cherry
        // Banana
    }
}

二、Set的底层确实是Map吗?(代码证明)

**是的,100%确定。**​ 让我们从源码角度证明:

1. HashSet 的源码片段
复制代码
// JDK 中的 HashSet 源码(简化版)
public class HashSet<E> {
    private transient HashMap<E, Object> map; // 关键:内部维护一个HashMap
    
    // 虚拟的Value值,所有Key共享这一个对象
    private static final Object PRESENT = new Object();
    
    public HashSet() {
        map = new HashMap<>(); // 构造时创建HashMap
    }
    
    public boolean add(E e) {
        return map.put(e, PRESENT) == null; // 将元素作为Key,PRESENT作为Value放入HashMap
    }
    
    public boolean remove(Object o) {
        return map.remove(o) == PRESENT; // 从HashMap中移除Key
    }
}
2. TreeSet 的源码片段
复制代码
// JDK 中的 TreeSet 源码(简化版)
public class TreeSet<E> {
    private transient NavigableMap<E, Object> m; // 维护一个NavigableMap(TreeMap实现了它)
    
    private static final Object PRESENT = new Object();
    
    public TreeSet() {
        this(new TreeMap<E, Object>()); // 构造时创建TreeMap
    }
    
    TreeSet(NavigableMap<E, Object> m) {
        this.m = m;
    }
    
    public boolean add(E e) {
        return m.put(e, PRESENT) == null; // 同样,元素作为Key
    }
}
3. 自己模拟实现(最直观的理解)
复制代码
// 模拟一个 "MyHashSet",使用 HashMap 作为底层存储
class MyHashSet<E> {
    private HashMap<E, Object> map;
    private static final Object DUMMY = new Object(); // 虚拟值
    
    public MyHashSet() {
        map = new HashMap<>();
    }
    
    public boolean add(E element) {
        // 如果map中已经存在这个key,put会返回旧值(不是null)
        // 如果不存在,put返回null
        return map.put(element, DUMMY) == null;
    }
    
    public boolean contains(E element) {
        return map.containsKey(element);
    }
    
    public boolean remove(E element) {
        return map.remove(element) == DUMMY;
    }
    
    public int size() {
        return map.size();
    }
}

public class SetIsMapDemo {
    public static void main(String[] args) {
        MyHashSet<String> mySet = new MyHashSet<>();
        
        System.out.println("添加 Apple: " + mySet.add("Apple")); // true
        System.out.println("添加 Banana: " + mySet.add("Banana")); // true
        System.out.println("再次添加 Apple: " + mySet.add("Apple")); // false(已存在)
        
        System.out.println("集合大小: " + mySet.size()); // 2
        System.out.println("包含 Banana? " + mySet.contains("Banana")); // true
    }
}

三、为什么Set要用Map实现?

这是一个非常巧妙的设计:

  1. 代码复用:不需要重新实现哈希算法或红黑树,直接复用Map的成熟实现。

  2. Set的特性 :Set的核心要求就是元素不重复 ,这正好对应Map的Key不重复特性。

  3. 简单高效 :只需要一个固定的虚拟对象(PRESENT)作为Value,所有Key共享这一个Value,节省内存。

总结表格

集合类 底层实现 虚拟Value对象 本质
HashSet HashMap private static final Object PRESENT = new Object() 只使用HashMap的Key部分
TreeSet TreeMap 同上 只使用TreeMap的Key部分

所以当面试官问"Set的底层是Map吗",你可以自信地回答:是的,HashSet基于HashMap实现,TreeSet基于TreeMap实现,Set元素就是Map的Key,用一个固定的虚拟对象作为Value。

相关推荐
Scott9999HH1 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔1 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海2 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区6 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大6 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai7 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说7 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json