Java 集合框架核心类解析:HashMap、TreeMap、HashSet、TreeSet(含示例)

Java集合框架核心类详解

1. 概述

Java 集合框架中,HashMapTreeMap 属于 Map 接口(键值对存储),HashSetTreeSet 属于 Set 接口(单列元素存储,无重复)。其核心关系为:

  • HashSet 底层依赖 HashMap(元素存于 HashMap 的 Key,Value 用固定空对象占位);
  • TreeSet 底层依赖 TreeMap(同理,元素存于 TreeMap 的 Key)。

本文将从 底层原理(数据结构、哈希 / 排序机制、扩容 / 平衡策略)、核心用法底层运行过程演示 三方面逐一解析,并最终给出对比总结。

2. HashMap

2.1 底层原理

2.1.1 数据结构(JDK 1.8+)

采用 数组 + 链表 + 红黑树 混合结构:

  • 数组(哈希桶):核心存储结构,类型为 Node<K,V>[] table,默认初始容量 16(必须是 2 的幂,保证哈希计算高效);
  • 链表:解决哈希冲突(不同 Key 计算出相同索引),当链表长度超过阈值(默认 8)且数组长度 ≥ 64 时,转为红黑树;
  • 红黑树:优化长链表的查询效率(链表查询时间复杂度 O (n),红黑树 O (log n)),当树节点数少于 6 时,退化为链表。
2.1.2 哈希机制
  1. 哈希值计算:Key 的hashCode()经过扰动函数(HashMap.hash()方法)处理,减少哈希冲突概率:

    static final int hash(Object key) {
        int h;
        // key为null时hash值为0,固定存于数组索引0
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
  2. 索引计算:通过 (table.length - 1) & hash 得到数组索引(因数组长度是 2 的幂,& 等价于取模,效率更高)。

2.1.3 扩容与树化策略
  • 核心参数:
    • 初始容量:16(DEFAULT_INITIAL_CAPACITY);
    • 负载因子:0.75(DEFAULT_LOAD_FACTOR),平衡空间与时间效率;
    • 阈值(threshold):容量 × 负载因子,触发扩容的临界值。
  • 扩容机制:
    • size(实际元素数)> threshold 时,扩容为原容量的 2 倍(保证仍是 2 的幂);
    • 扩容时需重新计算所有元素的索引(JDK 1.8 优化:通过高位哈希值判断,无需重复计算 hash),并迁移至新数组。
  • 树化条件:
    • 链表长度 ≥ 8(TREEIFY_THRESHOLD);
    • 数组长度 ≥ 64(MIN_TREEIFY_CAPACITY);
    • 若数组长度不足 64,仅触发扩容而非树化。

2.2 核心用法

2.2.1 构造方法
// 1. 默认构造(容量16,负载因子0.75)
HashMap<K,V> map = new HashMap<>();
// 2. 指定初始容量
HashMap<K,V> map = new HashMap<>(32);
// 3. 指定初始容量和负载因子
HashMap<K,V> map = new HashMap<>(32, 0.8f);
// 4. 从其他Map复制
HashMap<K,V> map = new HashMap<>(otherMap);
2.2.2 常用 API
方法功能描述
put(K key, V val)新增 / 覆盖键值对(返回旧值)
get(Object key)根据 Key 获取 Value(无则返回 null)
remove(Object key)删除键值对(返回旧值)
containsKey(Object key)判断是否包含指定 Key
keySet()获取所有 Key 的 Set 集合
entrySet()获取键值对 Entry 的 Set 集合
size()获取元素个数

2.3 底层运行过程演示

目标:演示哈希冲突、链表、扩容、树化的完整过程
import java.lang.reflect.Field;
import java.util.HashMap;

public class HashMapDemo {
    // 自定义Key,重写hashCode制造哈希冲突
    static class ConflictKey {
        private int id;

        public ConflictKey(int id) {
            this.id = id;
        }

        // 让id%3相同的Key哈希值相同,强制冲突
        @Override
        public int hashCode() {
            return id % 3;
        }

        // 仅当id相同时视为同一Key
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o) return false;
            ConflictKey that = (ConflictKey) o;
            return id == that.id;
        }

        @Override
        public String toString() {
            return "Key{" + "id=" + id + "}";
        }
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        HashMap<ConflictKey, String> map = new HashMap<>();
        Field tableField = HashMap.class.getDeclaredField("table");
        tableField.setAccessible(true); // 反射获取table数组

        // 第一步:放入9个元素,制造哈希冲突(索引0、1、2各3个元素)
        System.out.println("=== 第一步:放入9个冲突元素 ===");
        for (int i = 1; i <= 9; i++) {
            map.put(new ConflictKey(i), "val" + i);
        }
        Object[] table = (Object[]) tableField.get(map);
        System.out.println("数组长度:" + table.length); // 初始16(未扩容,size=9 < 16*0.75=12)
        System.out.println("索引1的桶(链表):");
        traverseNode((HashMap.Node) table[1]); // 输出Key{id=1} -> Key{id=4} -> Key{id=7}

        // 第二步:再放入5个元素,触发扩容(size=14 > 12)
        System.out.println("\n=== 第二步:放入5个元素,触发扩容 ===");
        for (int i = 10; i <= 14; i++) {
            map.put(new ConflictKey(i), "val" + i);
        }
        table = (Object[]) tableField.get(map);
        System.out.println("扩容后数组长度:" + table.length); // 32(16*2)

        // 第三步:继续放入元素,触发树化(索引1的链表长度≥8,数组长度≥64)
        System.out.println("\n=== 第三步:放入足够元素,触发树化 ===");
        for (int i = 15; i <= 30; i++) {
            map.put(new ConflictKey(i), "val" + i);
        }
        table = (Object[]) tableField.get(map);
        System.out.println("最终数组长度:" + table.length); // 64(32*2)
        System.out.println("索引1的桶类型:" + table[1].getClass().getSimpleName()); // TreeNode(红黑树节点)
    }

    // 遍历链表节点
    private static void traverseNode(HashMap.Node node) {
        while (node != null) {
            System.out.print(node.getKey() + " -> ");
            node = node.next;
        }
        System.out.println("null");
    }
}
运行结果解析:
  1. 初始阶段:数组长度 16,3 个 Key 共享一个索引(哈希冲突),形成链表;
  2. 扩容阶段:元素数超过阈值 12,数组扩容为 32;
  3. 树化阶段:继续添加元素后,数组长度达到 64,且索引 1 的链表长度 ≥8,自动转为红黑树。

3. TreeMap

3.1 底层原理

3.1.1 数据结构

采用 红黑树(自平衡的二叉搜索树)作为唯一存储结构,每个节点为 TreeNode<K,V>,包含:

  • Key、Value;
  • 左子树(left)、右子树(right)、父节点(parent);
  • 颜色标记(red/black),用于维持树的平衡。
3.1.2 排序机制

TreeMap 是 有序 Map,排序规则二选一(必须指定,否则 Key 需实现 Comparable):

  1. 自然排序:Key 实现 Comparable<K> 接口,重写 compareTo(K o) 方法(如 String、Integer 等内置类);
  2. 定制排序:构造方法传入 Comparator<K> 接口实现类,自定义排序逻辑。
3.1.3 平衡策略

红黑树通过以下规则保证平衡(插入 / 删除后触发):

  1. 每个节点非红即黑;
  2. 根节点为黑;
  3. 所有叶子节点(NIL)为黑;
  4. 红色节点的子节点必为黑;
  5. 从任一节点到其叶子节点的所有路径,黑节点数相同。

若违反规则,通过 旋转(左旋 / 右旋)变色 调整,确保树的高度维持在 O (log n),保证查询 / 插入 / 删除效率。

3.1.4 无扩容概念

红黑树是动态调整的树形结构,无需数组扩容,节点随元素增删动态插入 / 删除并自平衡。

3.2 核心用法

3.2.1 构造方法
// 1. 自然排序(Key必须实现Comparable)
TreeMap<K,V> treeMap = new TreeMap<>();
// 2. 定制排序(传入Comparator)
TreeMap<K,V> treeMap = new TreeMap<>(Comparator.comparingInt(User::getAge));
// 3. 从其他Map复制(继承排序规则)
TreeMap<K,V> treeMap = new TreeMap<>(otherSortedMap);
3.2.2 常用 API(新增排序相关方法)
方法功能描述
put(K key, V val)按排序规则插入键值对(Key 重复则覆盖)
get(Object key)根据 Key 获取 Value
firstKey()获取最小 Key(排序后第一个)
lastKey()获取最大 Key(排序后最后一个)
subMap(K fromKey, K toKey)获取区间 [fromKey, toKey) 的子 Map
headMap(K toKey)获取 Key < toKey 的子 Map
tailMap(K fromKey)获取 Key ≥ fromKey 的子 Map

3.3 底层运行过程演示

目标:演示自然排序、定制排序及红黑树平衡特性
import java.util.TreeMap;

public class TreeMapDemo {
    // 自定义User类,用于演示排序
    static class User {
        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        // getter
        public int getAge() { return age; }
        public String getName() { return name; }

        @Override
        public String toString() {
            return "User{name='" + name + "', age=" + age + "}";
        }
    }

    public static void main(String[] args) {
        // 1. 定制排序:按年龄升序(年龄相同则按姓名字典序)
        TreeMap<User, String> ageSortedMap = new TreeMap<>((u1, u2) -> {
            if (u1.getAge() != u2.getAge()) {
                return u1.getAge() - u2.getAge(); // 年龄升序
            }
            return u1.getName().compareTo(u2.getName()); // 姓名字典序
        });

        // 插入元素(触发红黑树插入与平衡)
        ageSortedMap.put(new User("Bob", 22), "设计师");
        ageSortedMap.put(new User("Alice", 25), "工程师");
        ageSortedMap.put(new User("David", 25), "测试工程师"); // 年龄相同,按姓名排序
        ageSortedMap.put(new User("Charlie", 30), "产品经理");

        System.out.println("=== 按年龄升序遍历(红黑树有序输出)===");
        ageSortedMap.forEach((user, job) -> System.out.println(user + " -> " + job));
        // 输出顺序:Bob(22) → Alice(25) → David(25) → Charlie(30)

        // 2. 自然排序:String按字典序
        TreeMap<String, Integer> strSortedMap = new TreeMap<>();
        strSortedMap.put("banana", 3);
        strSortedMap.put("apple", 1);
        strSortedMap.put("cherry", 5);

        System.out.println("\n=== String自然排序(字典序)===");
        System.out.println("最小Key:" + strSortedMap.firstKey()); // apple
        System.out.println("最大Key:" + strSortedMap.lastKey()); // cherry
        System.out.println("区间[apple, cherry):" + strSortedMap.subMap("apple", "cherry")); // {apple=1, banana=3}
    }
}
运行结果解析:
  1. 定制排序中,元素按年龄升序排列,年龄相同时按姓名字典序,体现红黑树的有序性;
  2. 自然排序中,String 按字典序排列,通过 firstKey()subMap() 等方法可高效获取有序子集,底层依赖红黑树的快速查找特性。

4. HashSet

4.1 底层原理

4.1.1 数据结构

完全依赖 HashMap

  • HashSet 内部维护一个 HashMap 实例(map);
  • 元素存储在 map 的 Key 位置;
  • Value 固定为一个静态常量 PRESENT = new Object()(占位,无实际意义)。

因此,HashSet 的数据结构、哈希机制、扩容策略与 HashMap 完全一致(数组 + 链表 + 红黑树)。

4.1.2 去重原理

依赖 HashMap 的 Key 去重规则:

  1. 先计算元素的 hashCode(),判断是否存在哈希冲突;
  2. 若哈希值不同,直接插入;
  3. 若哈希值相同,通过 equals() 方法判断是否为同一元素;
  4. equals() 返回 true,视为重复元素,不插入;否则插入链表 / 红黑树。

4.2 核心用法

4.2.1 构造方法
// 1. 默认构造(底层HashMap容量16,负载因子0.75)
HashSet<E> set = new HashSet<>();
// 2. 指定初始容量
HashSet<E> set = new HashSet<>(32);
// 3. 指定初始容量和负载因子
HashSet<E> set = new HashSet<>(32, 0.8f);
// 4. 从其他Collection复制
HashSet<E> set = new HashSet<>(otherCollection);
4.2.2 常用 API
方法功能描述
add(E e)添加元素(重复则返回 false)
remove(Object o)删除元素(存在则返回 true)
contains(Object o)判断是否包含元素
size()获取元素个数
isEmpty()判断是否为空

4.3 底层运行过程演示

目标:演示去重机制与哈希冲突处理
import java.util.HashSet;
import java.util.Objects;

public class HashSetDemo {
    // 自定义Student类,重写hashCode和equals实现去重
    static class Student {
        private String studentId;
        private String name;

        public Student(String studentId, String name) {
            this.studentId = studentId;
            this.name = name;
        }

        // 关键:studentId相同则哈希值相同
        @Override
        public int hashCode() {
            return Objects.hash(studentId);
        }

        // 关键:studentId相同则视为同一学生(去重依据)
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o) return false;
            Student student = (Student) o;
            return Objects.equals(studentId, student.studentId);
        }

        @Override
        public String toString() {
            return "Student{id='" + studentId + "', name='" + name + "'}";
        }
    }

    public static void main(String[] args) {
        HashSet<Student> studentSet = new HashSet<>();

        // 添加3个学生,其中2个studentId相同(视为重复)
        studentSet.add(new Student("001", "张三"));
        studentSet.add(new Student("002", "李四"));
        boolean addResult = studentSet.add(new Student("001", "张三三")); // 重复,添加失败

        System.out.println("=== HashSet去重演示 ===");
        System.out.println("添加重复学生返回值:" + addResult); // false
        System.out.println("集合大小:" + studentSet.size()); // 2(去重后)
        System.out.println("集合元素:");
        studentSet.forEach(System.out::println);
        // 输出:Student{id='001', name='张三'}, Student{id='002', name='李四'}

        // 演示哈希冲突(底层HashMap的链表/红黑树)
        HashSet<ConflictKey> conflictSet = new HashSet<>();
        for (int i = 1; i <= 10; i++) {
            conflictSet.add(new ConflictKey(i)); // ConflictKey与HashMapDemo中一致,制造冲突
        }
        System.out.println("\n=== 哈希冲突处理(底层链表)===");
        System.out.println("冲突集合大小:" + conflictSet.size()); // 10(无重复)
    }

    // 复用HashMapDemo中的ConflictKey,制造哈希冲突
    static class ConflictKey {
        private int id;
        public ConflictKey(int id) { this.id = id; }
        @Override
        public int hashCode() { return id % 3; }
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o) return false;
            ConflictKey that = (ConflictKey) o;
            return id == that.id;
        }
    }
}
运行结果解析:
  1. 去重机制:studentId="001" 的两个学生被视为重复,第二个添加失败,体现 hashCode() + equals() 的核心作用;
  2. 哈希冲突:ConflictKeyhashCode() 重复,但 equals() 不同,底层 HashMap 会将其存入同一索引的链表中,HashSet 正常存储所有元素(无重复)。

5. TreeSet

5.1 底层原理

5.1.1 数据结构

完全依赖 TreeMap

  • 内部维护一个 TreeMap 实例(m);
  • 元素存储在 m 的 Key 位置;
  • Value 固定为静态常量 PRESENT = new Object()(占位)。

因此,TreeSet 的数据结构(红黑树)、排序机制、平衡策略与 TreeMap 完全一致。

5.1.2 去重原理

依赖 TreeMap 的 Key 去重规则:

  • 基于排序规则判断元素是否重复:
    1. 自然排序:通过 compareTo(K o) 方法,返回 0 则视为重复;
    2. 定制排序:通过 Comparator.compare(K o1, K o2) 方法,返回 0 则视为重复。

5.2 核心用法

5.2.1 构造方法
// 1. 自然排序(元素必须实现Comparable)
TreeSet<E> set = new TreeSet<>();
// 2. 定制排序(传入Comparator)
TreeSet<E> set = new TreeSet<>(Comparator.comparingInt(String::length));
// 3. 从其他Collection复制
TreeSet<E> set = new TreeSet<>(otherCollection);
5.2.2 常用 API(新增排序相关方法)
方法功能描述
add(E e)按排序规则添加元素(重复则返回 false)
remove(Object o)删除元素(存在则返回 true)
first()获取最小元素
last()获取最大元素
subSet(E fromElement, E toElement)获取区间 [from, to) 的子集
headSet(E toElement)获取元素 < toElement 的子集

5.3 底层运行过程演示

目标:演示排序与去重机制
import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        // 1. 定制排序:按字符串长度降序(长度相同则按字典序)
        TreeSet<String> lengthSortedSet = new TreeSet<>((s1, s2) -> {
            if (s1.length() != s2.length()) {
                return s2.length() - s1.length(); // 长度降序
            }
            return s1.compareTo(s2); // 长度相同则字典序
        });

        // 添加元素(触发红黑树排序与去重)
        lengthSortedSet.add("apple"); // 5
        lengthSortedSet.add("banana"); // 6
        lengthSortedSet.add("cherry"); // 6
        lengthSortedSet.add("date"); // 4
        lengthSortedSet.add("banana"); // 重复,添加失败

        System.out.println("=== 按字符串长度降序遍历 ===");
        lengthSortedSet.forEach(System.out::println);
        // 输出:banana → cherry → apple → date

        // 2. 自然排序:Integer按数值升序
        TreeSet<Integer> numSet = new TreeSet<>();
        numSet.add(5);
        numSet.add(2);
        numSet.add(8);
        numSet.add(2); // 重复,添加失败

        System.out.println("\n=== Integer自然排序(数值升序)===");
        System.out.println("最小元素:" + numSet.first()); // 2
        System.out.println("最大元素:" + numSet.last()); // 8
        System.out.println("区间[2, 8):" + numSet.subSet(2, 8)); // [2,5]
    }
}
运行结果解析:
  1. 定制排序中,元素按长度降序排列,长度相同时按字典序,重复元素(“banana”)添加失败;
  2. 自然排序中,Integer 按数值升序排列,通过 first()subSet() 可高效获取有序子集,底层依赖红黑树的有序性和平衡特性。

6. 核心对比总结

特性HashMapTreeMapHashSetTreeSet
底层结构数组 + 链表 + 红黑树红黑树基于 HashMap(同左)基于 TreeMap(红黑树)
有序性无序(插入顺序≠遍历顺序)有序(自然 / 定制排序)无序有序(自然 / 定制排序)
去重依据Key 的 hashCode ()+equals ()Key 的 compareTo/compare ()元素的 hashCode ()+equals ()元素的 compareTo/compare ()
扩容机制容量 2 倍扩容(阈值 = 容量 × 负载因子)无扩容(红黑树自平衡)同 HashMap无扩容(同 TreeMap)
元素 / Key 限制Key 可 null(仅 1 个)Key 不可 null(排序冲突)元素可 null(仅 1 个)元素不可 null(排序冲突)
时间复杂度插入 / 查询 / 删除 O (1)(平均)插入 / 查询 / 删除 O (log n)同 HashMap同 TreeMap
适用场景快速查询、无排序需求有序查询、区间查询快速去重、无排序需求有序去重、区间查询

7. 关键注意事项

  1. HashMap/HashSet 去重:必须重写 hashCode()equals(),且逻辑一致(equals() 为 true 时,hashCode() 必须相同);
  2. TreeMap/TreeSet 排序:Key / 元素要么实现 Comparable,要么构造时传入 Comparator,否则抛出 ClassCastException
  3. HashMap 负载因子:默认 0.75,值越大空间利用率越高但冲突概率越大,值越小冲突越少但空间浪费越多;
  4. 线程安全:四个类均非线程安全,多线程环境需使用 Collections.synchronizedMap()/synchronizedSet()ConcurrentHashMap/CopyOnWriteArraySet
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白岁山太奶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值