hashSet的构造方法详解


什么是 HashSet

先简单铺个底:HashSet 是 Java 集合框架里的一个类,基于 HashMap 实现,特点是:

  • 无序:元素没有固定顺序。
  • 唯一:不允许重复元素(通过 hashCodeequals 判断)。
  • 高效:增删查操作平均时间复杂度是 O(1),适合快速查找和去重。

HashSet 的构造方法决定了它的初始状态(容量、加载因子、初始元素),直接影响性能和内存使用。下面我逐一拆解所有构造方法(基于 Java 17)。


HashSet 的构造方法

HashSet4 个构造方法,我按从简单到复杂讲,每个方法都会说明:

  • 语法和参数
  • 内部实现
  • 使用场景
  • 注意事项
  • 示例代码
1. HashSet()
public HashSet()
  • 作用
    创建一个空的 HashSet,使用默认初始容量 16 和默认加载因子 0.75
  • 参数:无。
  • 内部实现
    • 创建一个底层 HashMap 实例,容量设为 16,加载因子 0.75。
    • HashSet 的元素实际存到 HashMap 的键(key)里,值是固定的 Object(占位)。
    • 源码(简化版):
      public HashSet() {
          map = new HashMap<>();
      }
      
      HashMap 默认初始容量 16,加载因子 0.75)
  • 加载因子
    • 加载因子 = 元素数量 ÷ 容量。
    • 当元素数量超过 容量 × 加载因子(16 × 0.75 = 12)时,HashSet 扩容(通常容量翻倍到 32)。
  • 使用场景
    • 不确定元素数量。
    • 小规模数据(几十个元素),默认设置够用。
    • 快速上手,省心。
  • 注意事项
    • 如果元素很多(比如上千),频繁扩容会降低性能,建议用带初始容量的构造方法。
    • 默认容量 16 可能偏小,内存敏感场景要优化。
  • 例子
    HashSet<String> set = new HashSet<>();
    set.add("炒鸡VIP");
    set.add("普通VIP");
    set.add("炒鸡VIP"); // 重复元素,不会添加
    System.out.println(set); // 输出:[普通VIP, 炒鸡VIP](顺序随机)
    
2. HashSet(int initialCapacity)
public HashSet(int initialCapacity)
  • 作用
    创建空的 HashSet,指定初始容量,默认加载因子 0.75
  • 参数
    • initialCapacity:初始容量(底层 HashMap 的桶数),必须是非负数(≥ 0)。
    • 如果 initialCapacity < 0,抛 IllegalArgumentException
  • 内部实现
    • 创建 HashMap,容量设为 initialCapacity(会向上调整为 2 的幂,比如 100 调整为 128)。
    • 加载因子仍是 0.75。
    • 源码(简化版):
      public HashSet(int initialCapacity) {
          map = new HashMap<>(initialCapacity);
      }
      
  • 使用场景
    • 知道大概元素数量,提前设容量避免扩容。
    • 比如要存 100 个元素,设 initialCapacity = 100,性能更好。
  • 注意事项
    • 容量不是严格等于 initialCapacity,而是 >= initialCapacity 的最小 2 的幂(HashMap 要求)。
    • 设太小,频繁扩容浪费性能;设太大,浪费内存。
    • 建议容量设为:预计元素数 ÷ 加载因子 + 1(比如 100 个元素,设 100 / 0.75 ≈ 134)。
  • 例子
    HashSet<String> set = new HashSet<>(100); // 初始容量 100(实际 128)
    set.add("炒鸡VIP");
    System.out.println(set.size()); // 输出:1
    
3. HashSet(int initialCapacity, float loadFactor)
public HashSet(int initialCapacity, float loadFactor)
  • 作用
    创建空的 HashSet,指定初始容量和加载因子。
  • 参数
    • initialCapacity:初始容量(≥ 0),否则抛 IllegalArgumentException
    • loadFactor:加载因子(通常 0.0 到 1.0),决定扩容时机,否则抛 IllegalArgumentException
  • 内部实现
    • 创建 HashMap,用指定的 initialCapacityloadFactor
    • 容量仍调整为 2 的幂。
    • 源码(简化版):
      public HashSet(int initialCapacity, float loadFactor) {
          map = new HashMap<>(initialCapacity, loadFactor);
      }
      
  • 加载因子影响
    • loadFactor 小(比如 0.5):扩容早,内存用得多,哈希冲突少,查找快。
    • loadFactor 大(比如 0.9):扩容晚,省内存,冲突多,查找稍慢。
  • 使用场景
    • 性能敏感场景,需精细调优。
    • 比如内存紧张时用高加载因子(0.9),追求速度时用低加载因子(0.5)。
  • 注意事项
    • loadFactor 太小(接近 0),扩容过于频繁,内存浪费严重。
    • loadFactor 太大(> 1.0),冲突多,性能下降。
    • 默认 0.75 是内存和性能的平衡点,改动要谨慎。
  • 例子
    HashSet<String> set = new HashSet<>(100, 0.9f); // 容量 100,加载因子 0.9
    set.add("炒鸡VIP");
    
4. HashSet(Collection<? extends E> c)
public HashSet(Collection<? extends E> c)
  • 作用
    创建 HashSet,并把另一个集合 c 的所有元素加进来(自动去重)。
  • 参数
    • c:任意实现了 Collection 接口的集合(比如 ListSetQueue)。
    • <? extends E>:表示 c 的元素类型必须是 E 或其子类(泛型约束)。
  • 内部实现
    • 计算初始容量:Math.max((int) (c.size() / .75f) + 1, 16),确保能装下 c 的元素。
    • 创建 HashMap,加载因子 0.75。
    • 调用 addAll(c),把 c 的元素逐个加入(重复元素被忽略)。
    • 源码(简化版):
      public HashSet(Collection<? extends E> c) {
          map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
          addAll(c);
      }
      
  • 使用场景
    • 从现有集合(比如 ArrayList)初始化 HashSet
    • 快速去重(比如把 List 转成无重复的 Set)。
  • 注意事项
    • 如果 cnull,抛 NullPointerException
    • c 的大小影响初始容量,过大可能浪费内存。
    • 元素顺序不保留(HashSet 无序)。
  • 例子
    List<String> list = Arrays.asList("炒鸡VIP", "普通VIP", "炒鸡VIP");
    HashSet<String> set = new HashSet<>(list); // 去重
    System.out.println(set); // 输出:[普通VIP, 炒鸡VIP](顺序随机)
    

关键概念和注意事项

  1. 底层是 HashMap

    • HashSet 的元素存到 HashMap 的键,值是个固定对象(PRESENT = new Object())。
    • 构造方法本质是初始化这个 HashMap
  2. 容量和扩容

    • 容量是底层 HashMap 的桶数,总是 2 的幂。
    • 扩容触发:元素数 > 容量 × 加载因子
    • 扩容代价高(重新分配所有元素),所以初始容量设合理很关键。
  3. 性能优化

    • 初始容量:设为 预计元素数 ÷ 加载因子 + 1
      • 比如存 1000 个元素,1000 / 0.75 ≈ 1334,设 new HashSet<>(1334)
    • 加载因子:默认 0.75 适合大部分场景,改动需测试。
    • 避免频繁扩容:扩容涉及重新哈希,耗时。
  4. 线程安全

    • HashSet 非线程安全。多线程操作要加锁:
      Set<String> set = Collections.synchronizedSet(new HashSet<>());
      
    • 或者用 ConcurrentHashMap.newKeySet()(Java 8+):
      Set<String> set = ConcurrentHashMap.newKeySet();
      
  5. 泛型

    • 总是用泛型,比如 HashSet<String>HashSet<Car>,避免类型转换麻烦。
    • 比如你之前代码可能用 HashSet<Car>CarjiFendengJi 字段:
      HashSet<Car> mry = new HashSet<>();
      
  6. 空元素

    • HashSet 允许 一个 null 元素(因为 HashMap 允许一个 null 键)。
    • 添加多个 null 会被去重。

结合实际场景

假设你要用 HashSet 存你之前代码里的 mry(比如 Car 对象,带 jiFendengJi),选择构造方法的思路:

  • 不知道元素数量:用 new HashSet<Car>(),默认 16 容量。
  • 预计 1000 个 Car:用 new HashSet<Car>(1334)(1000 / 0.75 ≈ 1334)。
  • List<Car> 初始化
    List<Car> carList = Arrays.asList(new Car(1500, ""), new Car(800, ""));
    HashSet<Car> mry = new HashSet<>(carList);
    
  • 内存敏感:用 new HashSet<Car>(1000, 0.9f),减少扩容。

你的遍历代码可以直接用增强 for

HashSet<Car> mry = new HashSet<>();
for (Car car : mry) {
    if (car.jiFen > 1200) {
        car.dengJi = "炒鸡VIP";
    }
}

常见问题和解答

  1. 为啥容量要是 2 的幂?

    • HashMap 用位运算(hash & (capacity - 1))计算桶索引,2 的幂效率高。
    • 比如你设 100,实际用 128(2^7)。
  2. 怎么选初始容量?

    • 估算元素数,除以加载因子(默认 0.75),向上取整。
    • 比如 100 个元素,100 / 0.75 ≈ 134,设 134,实际用 256(2^8)。
  3. 加载因子改了有啥影响?

    • 低加载因子(0.5):更少哈希冲突,查询快,内存多。
    • 高加载因子(0.9):省内存,冲突多,查询稍慢。
  4. 构造方法会影响去重吗?

    • 不会!去重靠 hashCodeequals,跟构造方法无关。
    • 确保 Car 类重写 hashCodeequals,比如:
      class Car {
          int jiFen;
          String dengJi;
          @Override
          public boolean equals(Object o) { /* 比较 jiFen 和 dengJi */ }
          @Override
          public int hashCode() { /* 用 jiFen 和 dengJi 计算 */ }
      }
      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值