HashSet 底层实现详解

【Java】如何保证集合的线程安全?


HashSet 的底层是基于 HashMap 实现的,让我们通过图示和代码来详细理解:

1. HashSet 整体结构

底层实际结构
HashSet 表面
委托给
键值对存储
HashMap
键: 元素值, 值: PRESENT
addE element
HashSet
removeObject obj
containsObject obj
PRESENT: 静态Object对象

2. 核心源码解析

// HashSet 的关键源码
public class HashSet<E> extends AbstractSet<E> 
                       implements Set<E>, Cloneable, java.io.Serializable {
    
    // 底层使用 HashMap 存储
    private transient HashMap<E, Object> map;
    
    // 虚拟值,所有键都映射到这个对象
    private static final Object PRESENT = new Object();
    
    // 构造方法 - 创建底层 HashMap
    public HashSet() {
        map = new HashMap<>();
    }
    
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
    
    // 添加元素 - 实际上是把元素作为 key 放入 HashMap
    public boolean add(E e) {
        return map.put(e, PRESENT) == null;
    }
    
    // 删除元素
    public boolean remove(Object o) {
        return map.remove(o) == PRESENT;
    }
    
    // 检查包含
    public boolean contains(Object o) {
        return map.containsKey(o);
    }
    
    // 大小
    public int size() {
        return map.size();
    }
}

3. 添加元素的过程

User HashSet HashMap add("Apple") put("Apple", PRESENT) 计算hash("Apple") 找到对应的桶位置 检查是否已存在 如果不存在则插入新节点 如果存在则覆盖值 返回旧值(null表示新增) true(新增成功) User HashSet HashMap

4. 内存结构示意图

PRESENT 对象
底层 HashMap 结构
HashSet 实例
静态Object实例
所有键共享同一个值
HashMap table数组
桶0: null
桶1: null
桶2: Node
桶3: null
桶4: Node
桶5: null
Node: key=Banana
value=PRESENT
next=null
Node: key=Apple
value=PRESENT
next=→
Node: key=Orange
value=PRESENT
next=null
HashSet set = new HashSet<>

5. 完整示例代码

import java.util.HashSet;
import java.util.HashMap;

public class HashSetInternal {
    public static void main(String[] args) {
        // 创建 HashSet
        HashSet<String> set = new HashSet<>();
        
        // 添加元素 - 底层调用 HashMap.put(element, PRESENT)
        set.add("Apple");
        set.add("Banana");
        set.add("Apple"); // 重复元素,不会添加
        
        // 删除元素 - 底层调用 HashMap.remove(element)
        set.remove("Banana");
        
        // 检查包含 - 底层调用 HashMap.containsKey(element)
        boolean hasApple = set.contains("Apple");
        
        System.out.println("Set size: " + set.size()); // 1
        System.out.println("Contains Apple: " + hasApple); // true
        
        // 模拟 HashSet 的底层实现
        demonstrateInternalWorkings();
    }
    
    public static void demonstrateInternalWorkings() {
        System.out.println("\n=== HashSet 底层实现模拟 ===");
        
        // 这就是 HashSet 底层的 HashMap
        HashMap<String, Object> internalMap = new HashMap<>();
        final Object PRESENT = new Object(); // 模拟 PRESENT
        
        // 模拟 add 操作
        System.out.println("添加 Apple: " + (internalMap.put("Apple", PRESENT) == null));
        System.out.println("添加 Banana: " + (internalMap.put("Banana", PRESENT) == null));
        System.out.println("再次添加 Apple: " + (internalMap.put("Apple", PRESENT) == null));
        
        // 模拟 contains 操作
        System.out.println("包含 Apple: " + internalMap.containsKey("Apple"));
        
        // 模拟 remove 操作
        System.out.println("删除 Banana: " + (internalMap.remove("Banana") == PRESENT));
        
        System.out.println("最终Map大小: " + internalMap.size());
        System.out.println("Map内容: " + internalMap.keySet());
    }
}

6. HashSet 特性总结

特性实现原理说明
元素唯一性HashMap 键的唯一性相同的元素会产生相同的哈希值
允许 null 元素HashMap 允许 null 键可以添加一个 null 值
不保证顺序HashMap 不保证顺序元素顺序取决于哈希函数
非线程安全HashMap 非线程安全需要外部同步
快速查找HashMap 的 O(1) 查找基于哈希表的快速访问

7. HashSet 与其他 Set 实现对比

// 不同 Set 实现的底层结构
HashSet<String> hashSet = new HashSet<>();        // 底层: HashMap
LinkedHashSet<String> linkedSet = new LinkedHashSet<>(); // 底层: LinkedHashMap  
TreeSet<String> treeSet = new TreeSet<>();        // 底层: TreeMap (红黑树)

// 性能特点:
// - HashSet: 最快,但不保证顺序
// - LinkedHashSet: 保持插入顺序,稍慢
// - TreeSet: 自动排序,最慢

关键理解点:

  1. HashSet 只是 HashMap 的包装:所有操作都委托给 HashMap
  2. 值存储机制:所有元素作为 HashMap 的 key,value 都是同一个 PRESENT 对象
  3. 内存效率:PRESENT 是静态常量,所有 HashSet 实例共享,节省内存
  4. 性能特征:继承 HashMap 的所有特性,包括 O(1) 时间复杂度的基本操作
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐