第53章

Java Set接口详解

HashSet、LinkedHashSet和TreeSet的使用方法与应用场景

Set接口概述

Set接口是Java集合框架中的重要组成部分,继承自Collection接口。Set表示一个不包含重复元素的集合,是数学上集合概念的抽象。

Set接口的主要特点

// Set接口基本使用示例
Set<String> fruits = new HashSet<>();
fruits.add("苹果");
fruits.add("香蕉");
fruits.add("苹果"); // 重复元素,不会被添加

System.out.println(fruits); // 输出: [香蕉, 苹果]
System.out.println(fruits.size()); // 输出: 2

Set实现类对比

Java提供了三种主要的Set实现类,每种都有其特定的特点和适用场景:

实现类 排序特性 性能 允许null 线程安全 适用场景
HashSet 无序 最高 一般用途,高性能需求
LinkedHashSet 插入顺序 中等 需要保持插入顺序
TreeSet 自然排序 较低 需要排序和范围查询

HashSet详解

高性能特点

  • 基于哈希表实现
  • 平均O(1)时间复杂度
  • 查找、插入、删除操作快速
  • 内存使用效率高

无序存储

  • 不保证元素的迭代顺序
  • 元素位置由哈希值决定
  • 适合不关心顺序的场景
  • 大多数情况下的首选
// HashSet使用示例
HashSet<String> cities = new HashSet<>();
cities.add("北京");
cities.add("上海");
cities.add("广州");
cities.add("北京"); // 重复元素

System.out.println(cities); // 输出顺序不确定
System.out.println(cities.contains("北京")); // 快速查找: true

LinkedHashSet详解

LinkedHashSet继承自HashSet,通过维护一个双向链表来保持元素的插入顺序。

有序特性

  • 保持元素插入顺序
  • 可预测的迭代顺序
  • 适合缓存、历史记录
  • 性能略低于HashSet

应用场景

  • 浏览历史记录
  • 购物车商品去重
  • 标签系统
  • 需要保持顺序的集合
// LinkedHashSet保持插入顺序
LinkedHashSet<String> orderedSet = new LinkedHashSet<>();
orderedSet.add("第一个");
orderedSet.add("第二个");
orderedSet.add("第三个");

System.out.println(orderedSet); // 输出: [第一个, 第二个, 第三个]

// 浏览历史示例
LinkedHashSet<String> history = new LinkedHashSet<>();
history.add("首页");
history.add("产品页");
history.add("首页"); // 重复访问,不改变顺序
System.out.println(history); // [首页, 产品页]

TreeSet详解

TreeSet基于红黑树(自平衡二叉搜索树)实现,提供了自动排序功能和丰富的导航方法。

自动排序

  • 元素自动排序
  • 支持自然排序
  • 支持自定义比较器
  • O(log n)时间复杂度

导航功能

  • first()、last()方法
  • lower()、higher()方法
  • subSet()范围查询
  • headSet()、tailSet()方法
// TreeSet自动排序
TreeSet<Integer> numbers = new TreeSet<>();
numbers.add(50);
numbers.add(20);
numbers.add(80);
numbers.add(10);

System.out.println(numbers); // 输出: [10, 20, 50, 80]
System.out.println(numbers.first()); // 最小值: 10
System.out.println(numbers.last());  // 最大值: 80

// 范围查询
System.out.println(numbers.subSet(20, 60)); // [20, 50]
System.out.println(numbers.headSet(50));    // [10, 20]
System.out.println(numbers.tailSet(50));    // [50, 80]

性能对比分析

性能特点总结

  • 添加操作:HashSet > LinkedHashSet > TreeSet
  • 查找操作:HashSet ≈ LinkedHashSet > TreeSet
  • 内存使用:HashSet < LinkedHashSet < TreeSet
  • 排序需求:只有TreeSet提供自动排序
// 性能测试示例代码
public class SetPerformanceTest {
    public static void testPerformance() {
        int size = 100000;
        
        // HashSet性能测试
        long start = System.currentTimeMillis();
        Set<Integer> hashSet = new HashSet<>();
        for (int i = 0; i < size; i++) {
            hashSet.add(i);
        }
        System.out.println("HashSet添加耗时: " + 
            (System.currentTimeMillis() - start) + "ms");
        
        // LinkedHashSet性能测试
        start = System.currentTimeMillis();
        Set<Integer> linkedSet = new LinkedHashSet<>();
        for (int i = 0; i < size; i++) {
            linkedSet.add(i);
        }
        System.out.println("LinkedHashSet添加耗时: " + 
            (System.currentTimeMillis() - start) + "ms");
        
        // TreeSet性能测试
        start = System.currentTimeMillis();
        Set<Integer> treeSet = new TreeSet<>();
        for (int i = 0; i < size; i++) {
            treeSet.add(i);
        }
        System.out.println("TreeSet添加耗时: " + 
            (System.currentTimeMillis() - start) + "ms");
    }
}

实际应用示例

1. 用户权限管理(HashSet)

// 用户权限管理系统
public class UserPermissionManager {
    private Set<String> permissions = new HashSet<>();
    
    public void addPermission(String permission) {
        permissions.add(permission);
    }
    
    public boolean hasPermission(String permission) {
        return permissions.contains(permission); // O(1)快速查找
    }
    
    public void displayPermissions() {
        System.out.println("用户权限: " + permissions);
    }
}

// 使用示例
UserPermissionManager manager = new UserPermissionManager();
manager.addPermission("READ");
manager.addPermission("WRITE");
manager.addPermission("DELETE");
manager.addPermission("READ"); // 重复权限

System.out.println(manager.hasPermission("WRITE")); // true

2. 浏览历史记录(LinkedHashSet)

// 浏览历史管理
public class BrowsingHistory {
    private LinkedHashSet<String> history = new LinkedHashSet<>();
    private final int maxSize = 10;
    
    public void visit(String page) {
        // 如果已存在,先删除再添加(更新到最新位置)
        if (history.contains(page)) {
            history.remove(page);
        }
        history.add(page);
        
        // 保持历史记录数量限制
        if (history.size() > maxSize) {
            String oldest = history.iterator().next();
            history.remove(oldest);
        }
    }
    
    public List<String> getRecentPages() {
        return new ArrayList<>(history);
    }
}

// 使用示例
BrowsingHistory history = new BrowsingHistory();
history.visit("首页");
history.visit("产品页");
history.visit("关于我们");
history.visit("首页"); // 重复访问,移到最新位置

System.out.println(history.getRecentPages()); // [产品页, 关于我们, 首页]

3. 排行榜系统(TreeSet)

// 游戏排行榜
public class GameLeaderboard {
    private TreeSet<Player> leaderboard = new TreeSet<>(
        Comparator.comparingInt(Player::getScore).reversed()
            .thenComparing(Player::getName)
    );
    
    public void addPlayer(Player player) {
        leaderboard.add(player);
    }
    
    public List<Player> getTopPlayers(int count) {
        return leaderboard.stream()
            .limit(count)
            .collect(Collectors.toList());
    }
    
    public Player getTopPlayer() {
        return leaderboard.first();
    }
    
    public Set<Player> getPlayersInRange(int minScore, int maxScore) {
        return leaderboard.subSet(
            new Player("", minScore),
            new Player("", maxScore + 1)
        );
    }
}

class Player {
    private String name;
    private int score;
    
    // 构造方法、getter、setter等
}
💻 查看完整代码 - 在线IDE体验

Set使用最佳实践

选择合适的实现

  • 一般情况优先选择HashSet
  • 需要保持顺序时选择LinkedHashSet
  • 需要排序时选择TreeSet
  • 考虑性能和内存使用

线程安全考虑

  • Set实现类都不是线程安全的
  • 使用Collections.synchronizedSet()
  • 或使用ConcurrentHashMap.newKeySet()
  • 考虑使用并发集合类

自定义对象使用

  • 重写equals()和hashCode()方法
  • TreeSet需要实现Comparable接口
  • 或提供自定义Comparator
  • 确保一致性和正确性

性能优化

  • 合理设置HashSet初始容量
  • 避免频繁的扩容操作
  • 选择合适的负载因子
  • 考虑使用专门的集合库

常见陷阱

  • HashSet元素顺序不确定,不要依赖迭代顺序
  • TreeSet不允许null值,会抛出NullPointerException
  • 自定义对象必须正确实现equals()和hashCode()
  • 修改已添加到Set中的对象可能导致不一致
  • 在多线程环境下需要考虑线程安全问题

本章小结