关于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。

相关推荐
马尔代夫哈哈哈1 小时前
Spring AOP
java·后端·spring
only-qi1 小时前
Java 包装器模式:告别“类爆炸“
java·开发语言
Yweir1 小时前
Java 接口测试框架 Restassured
java·开发语言
wangbing11251 小时前
开发指南141-类和字节数组转换
java·服务器·前端
~央千澈~1 小时前
抖音弹幕游戏开发之第15集:添加配置文件·优雅草云桧·卓伊凡
java·前端·python
肖。35487870941 小时前
html中onclick误区,后续变量会更改怎么办?
android·java·javascript·css·html
郝学胜-神的一滴1 小时前
Effective Modern C++ 条款39:一次事件通信的优雅解决方案
开发语言·数据结构·c++·算法·多线程·并发
香芋Yu1 小时前
【从零构建AI Code终端系统】02 -- Bash 工具:一切能力的基础
开发语言·bash·agent·claude
码云数智-园园1 小时前
Java Swing 界面美化与 JPanel 优化完全指南:从复古到现代的视觉革命
java·开发语言