TreeSet、HashSet和LinkedHashSet该如何选择? --《JAVA编程思想》 71

本文通过测试分析了Java集合框架中HashSet、TreeSet和LinkedHashSet在添加、查询、移除和遍历操作上的性能差异。测试结果显示,HashSet在添加和遍历上表现最优,而LinkedHashSet在查询和移除上更胜一筹,TreeSet由于排序特性在性能上相对较弱,但在需要有序集合时是理想选择。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们实现 Set 集合时,主要有 TreeSet、HashSet 和 LinkedHashSet 三种选择,我们该如何对它们进行选择呢?

今天和大家一起从添加、查询、移除、遍历四个方面对 Set 的子类进行测试,看看它们的性能究竟如何?

0.准备工作

首先使用泛型定义一个通用的测试模板 SetTest,将需要实际测试的方法抽象为 test() ,返回值为执行测试方法所花费的时间。

public abstract class SetTest<T extends Set> {
    private T set;


    public abstract long test();

    public SetTest(T set) {
        this.set = set;
    }

    public T getSet() {
        return set;
    }

    public void setSet(T set) {
        this.set = set;
    }
    
}

再准备一个执行测试方法的工具类 SetPerformanceTest ,内部提供生成测试数据的方法 getSet() 和 打印测试信息的方法 displayHead()。

public class SetPerformanceTest {

    public static <T extends Set> T getSet(T set, int size) {
        for (int i = 0; i < size; i++) {
            set.add(i);
        }
        return set;
    }

    public static <T> void displayHead(T t, String methodName) {
        System.out.println(t.getClass().getSimpleName() + "\t" + methodName);
        System.out.println("容器长度\t操作次数\t平均时间");
    }
    
}

接下来,开始编写不同性能场景的测试方法,后续编写的测试代码都是直接放入 SetPerformanceTest 中运行,通过计算方法执行的时间 / 操作次数 = 平均操作时间 ,来验证 Set 的性能。

1.添加

    /**
     *
     * @param set 测试容器
     * @param loops 循环次数
     * @param size 集合长度
     * @param <T> 指定继承自 Set 的泛型
     */
    public static <T extends Set> void add(T set, int loops, int size) {
        SetTest<T> setTest = new SetTest<T>(set) {
            @Override
            public long test() {
                long startTime = System.nanoTime();
                for (int i = 0; i < loops; i++) {
					//移除清空容器花费的时间,降低其对测试结果的影响
                    long clearStart = System.nanoTime();
                    set.clear();
                    long clearEnd = System.nanoTime();
                    startTime = startTime - (clearEnd - clearStart);
                    for (int j = 0; j < size; j++) {
                        set.add(j);
                    }
                }
                long endTime = System.nanoTime();
                return endTime - startTime;
            }
        };
        long operateTime = setTest.test();
        long count = loops * size;
        long avg = operateTime / count;
        System.out.println(size + "\t" + count + "\t" + avg);
    }

    public static void main(String[] args) {
        Integer[][] integers = {{10, 100000}, {30, 300000}, {50, 800000}};
        HashSet<Integer> hashSet = new HashSet<>();
        TreeSet<Integer> treeSet = new TreeSet<>();
        LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
        String methodName="add";
        displayHead(hashSet, methodName);
        for (int i = 0; i < integers.length; i++) {
            add(hashSet, integers[i][0],  integers[i][1]);
        }
        System.out.println();
        displayHead(treeSet,methodName);
        for (int i = 0; i < integers.length; i++) {
            add(treeSet, integers[i][0],  integers[i][1]);
        }
        System.out.println();
        displayHead(linkedHashSet, methodName);
        for (int i = 0; i < integers.length; i++) {
            add(linkedHashSet, integers[i][0],  integers[i][1]);
        }
    }
HashSet	add
容器长度	操作次数	平均时间
100000	1000000	44
300000	9000000	19
800000	40000000	19

TreeSet	add
容器长度	操作次数	平均时间
100000	1000000	159
300000	9000000	189
800000	40000000	158

LinkedHashSet	add
容器长度	操作次数	平均时间
100000	1000000	58
300000	9000000	32
800000	40000000	26

2.查询

    /**
     * @param set   测试容器
     * @param loops 循环次数
     * @param size  集合长度
     * @param <T>   指定继承自 Set 的泛型
     */
    public static <T extends Set> void contains(T set, int loops, int size) {
        SetTest<T> setTest = new SetTest<T>(set) {
            @Override
            public long test() {
                Random random = new Random();
                long startTime = System.nanoTime();
                for (int i = 0; i < loops; i++) {
                    set.contains(random.nextInt(size));
                }
                long endTime = System.nanoTime();
                return endTime - startTime;
            }
        };
        long operateTime = setTest.test();
        long count = loops;
        long avg = operateTime / count;
        System.out.println(size + "\t" + count + "\t" + avg);
    }
    
    public static void main(String[] args) {
        Integer[][] integers = {{1000, 100000}, {3000, 300000}, {5000, 800000}};
        HashSet<Integer> hashSet = new HashSet<>();
        TreeSet<Integer> treeSet = new TreeSet<>();
        LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
        String methodName="contains";
        displayHead(hashSet, methodName);
        for (int i = 0; i < integers.length; i++) {
            hashSet=getSet(hashSet,integers[i][1]);
            contains(hashSet, integers[i][0], integers[i][1]);
        }
        System.out.println();
        displayHead(treeSet, methodName);
        for (int i = 0; i < integers.length; i++) {
            treeSet=getSet(treeSet,integers[i][1]);
            contains(treeSet, integers[i][0], integers[i][1]);
        }
        System.out.println();
        displayHead(linkedHashSet, methodName);
        for (int i = 0; i < integers.length; i++) {
            linkedHashSet=getSet(linkedHashSet,integers[i][1]);
            contains(linkedHashSet, integers[i][0], integers[i][1]);
        }
    }
HashSet	contains
容器长度	操作次数	平均时间
100000	1000	976
300000	3000	238
800000	5000	272

TreeSet	contains
容器长度	操作次数	平均时间
100000	1000	1177
300000	3000	784
800000	5000	930

LinkedHashSet	contains
容器长度	操作次数	平均时间
100000	1000	134
300000	3000	177
800000	5000	263

3.移除

    /**
     * @param set   测试容器
     * @param loops 循环次数
     * @param size  集合长度
     * @param <T>   指定继承自 Set 的泛型
     */
    public static <T extends Set> void remove(T set, int loops, int size) {
        SetTest<T> setTest = new SetTest<T>(set) {
            @Override
            public long test() {
                Random random = new Random();
                long startTime = System.nanoTime();
                for (int i = 0; i < loops; i++) {
                    set.contains(random.nextInt(size - i));
                }
                long endTime = System.nanoTime();
                return endTime - startTime;
            }
        };
        long operateTime = setTest.test();
        long count = loops;
        long avg = operateTime / count;
        System.out.println(size + "\t" + count + "\t" + avg);
    }

    public static void main(String[] args) {
        Integer[][] integers = {{1000, 100000}, {3000, 300000}, {5000, 800000}};
        HashSet<Integer> hashSet = new HashSet<>();
        TreeSet<Integer> treeSet = new TreeSet<>();
        LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
        String methodName = "remove";
        displayHead(hashSet, methodName);
        for (int i = 0; i < integers.length; i++) {
            hashSet = getSet(hashSet, integers[i][1]);
            remove(hashSet, integers[i][0], integers[i][1]);
        }
        System.out.println();
        displayHead(treeSet, methodName);
        for (int i = 0; i < integers.length; i++) {
            treeSet = getSet(treeSet, integers[i][1]);
            remove(treeSet, integers[i][0], integers[i][1]);
        }
        System.out.println();
        displayHead(linkedHashSet, methodName);
        for (int i = 0; i < integers.length; i++) {
            linkedHashSet = getSet(linkedHashSet, integers[i][1]);
            remove(linkedHashSet, integers[i][0], integers[i][1]);
        }
    }
HashSet	remove
容器长度	操作次数	平均时间
100000	1000	681
300000	3000	268
800000	5000	286

TreeSet	remove
容器长度	操作次数	平均时间
100000	1000	1098
300000	3000	870
800000	5000	905

LinkedHashSet	remove
容器长度	操作次数	平均时间
100000	1000	132
300000	3000	177
800000	5000	256

4.遍历

    /**
     * @param set  测试容器
     * @param size 集合长度
     * @param <T>  指定继承自 Set 的泛型
     */
    public static <T extends Set> void iter(T set, int size) {
        SetTest<T> setTest = new SetTest<T>(set) {
            @Override
            public long test() {
                long startTime = System.nanoTime();
                Iterator iterator = set.iterator();
                while (iterator.hasNext()) {
                    iterator.next();
                }
                long endTime = System.nanoTime();
                return endTime - startTime;
            }
        };
        long operateTime = setTest.test();
        long count = size;
        long avg = operateTime / count;
        System.out.println(size + "\t" + size + "\t" + avg);
    }

    public static void main(String[] args) {
        Integer[] integers = {100000, 300000, 800000};
        HashSet<Integer> hashSet = new HashSet<>();
        TreeSet<Integer> treeSet = new TreeSet<>();
        LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
        String methodName = "iter";
        displayHead(hashSet, methodName);
        for (int i = 0; i < integers.length; i++) {
            hashSet = getSet(hashSet, integers[i]);
            iter(hashSet, integers[i]);
        }
        System.out.println();
        displayHead(treeSet, methodName);
        for (int i = 0; i < integers.length; i++) {
            treeSet = getSet(treeSet, integers[i]);
            iter(treeSet, integers[i]);
        }
        System.out.println();
        displayHead(linkedHashSet, methodName);
        for (int i = 0; i < integers.length; i++) {
            linkedHashSet = getSet(linkedHashSet, integers[i]);
            iter(linkedHashSet, integers[i]);
        }
    }
HashSet	iter
容器长度	操作次数	平均时间
100000	100000	80
300000	300000	12
800000	800000	7

TreeSet	iter
容器长度	操作次数	平均时间
100000	100000	70
300000	300000	25
800000	800000	16

LinkedHashSet	iter
容器长度	操作次数	平均时间
100000	100000	29
300000	300000	8
800000	800000	7

5.测试结果汇总

测试项目HashSetTreeSetLinkedHashSet
添加
查询
移除
遍历

小结

  1. HashSet 和 LinkedHashSet 在各方面的性能都优于 TreeSet ,但 TreeSet 内部支持排序,当你需要一个排序好的 Set 时,推荐使用 TreeSet。
  2. LinkedHashSet 保证了元素的插入顺序,在查询、移除和遍历方面性能优于 HashSet ,但因其底层数据结构为链表,在执行插入操作的时候维护成本较高,故性能不及 HashSet 。

本次分享至此结束,希望本文对你有所帮助,若能点亮下方的点赞按钮,在下感激不尽,谢谢您的【精神支持】。

若有任何疑问,也欢迎与我交流,若存在不足之处,也欢迎各位指正!

<think>好的,用户想了解JavaTreeSetHashSetLinkedHashSet的区别与联系。首先,我需要回忆这三个集合类的特性。 TreeSet基于红黑树实现,所以元素是有序的,支持自然排序或者通过Comparator自定义排序。但插入查询的时间复杂度都是O(log n),相比其他两种可能稍慢。另外,TreeSet不允许null元素,这点需要注意。 HashSet是基于哈希表实现的,使用HashMap作为底层存储。它的元素是无序的,插入查询的时间复杂度接近O(1)。不过,迭代顺序是不确定的。HashSet允许null元素,但只能有一个null,因为不允许重复。 LinkedHashSet继承自HashSet,内部通过链表维护插入顺序,因此迭代顺序是按照插入顺序来的。它的性能比HashSet略差,因为要维护链表,但比TreeSet好。同样允许null元素。 接下来,要比较它们的区别,包括底层数据结构、性能、排序特性、允许null的情况等。用户可能需要知道在不同场景下如何选择,比如需要排序就用TreeSet,需要快速访问且不关心顺序用HashSet,需要保持插入顺序用LinkedHashSet。 还要注意线程安全问题,这三个集合都不是线程安全的,如果在多线程环境下使用需要外部同步。另外,初始容量负载因子可能对HashSetLinkedHashSet的性能有影响,尤其是哈希冲突的情况。 可能需要提到它们都实现了Set接口,因此都不允许重复元素。在重复元素的判定上,都是根据equals()hashCode()方法,不过TreeSet还依赖compareTo或Comparator。 最后,生成相关问题时要考虑用户可能想深入了解每个集合的内部实现,或者性能优化的方法,以及具体的使用场景举例。</think>### TreeSetHashSetLinkedHashSet的区别与联系 #### 1. **底层数据结构** - **TreeSet**: 基于红黑树(平衡二叉搜索树)实现,元素按自然顺序或自定义比较器排序[^1]。 - **HashSet**: 基于哈希表(`HashMap`)实现,元素无序。 - **LinkedHashSet**: 继承自`HashSet`,底层通过哈希表+双向链表维护插入顺序[^2]。 #### 2. **元素唯一性** 三者均实现`Set`接口,保证元素唯一性(依赖`equals()``hashCode()`方法)。 #### 3. **排序特性** - **TreeSet**: 支持自然排序(如数值、字符串)或通过`Comparator`自定义排序。例如: $$ \text{元素集合} \Rightarrow \{1, 3, 5\} \text{ 会按升序排列} $$ - **HashSet**: 无序,迭代顺序可能与插入顺序无关。 - **LinkedHashSet**: 按插入顺序迭代。 #### 4. **性能对比** | 集合类型 | 插入/删除时间复杂度 | 查询时间复杂度 | 适用场景 | |----------------|---------------------|----------------|------------------------| | **TreeSet** | $O(\log n)$ | $O(\log n)$ | 需要有序元素的场景 | | **HashSet** | $O(1)$ (平均) | $O(1)$ (平均) | 快速访问,无需排序 | | **LinkedHashSet**| $O(1)$ (平均) | $O(1)$ (平均) | 需保留插入顺序的快速访问 | #### 5. **允许`null`值** - **TreeSet**: 不允许`null`(依赖排序规则,可能抛出`NullPointerException`)。 - **HashSet/LinkedHashSet**: 允许一个`null`元素。 #### 6. **代码示例** ```java // TreeSet示例(自动排序) Set<Integer> treeSet = new TreeSet<>(); treeSet.add(3); treeSet.add(1); System.out.println(treeSet); // 输出 [1, 3] // HashSet示例(无序) Set<String> hashSet = new HashSet<>(); hashSet.add("Banana"); hashSet.add("Apple"); System.out.println(hashSet); // 可能输出 [Apple, Banana] 或其他顺序 // LinkedHashSet示例(保留插入顺序) Set<String> linkedHashSet = new LinkedHashSet<>(); linkedHashSet.add("Banana"); linkedHashSet.add("Apple"); System.out.println(linkedHashSet); // 输出 [Banana, Apple] ``` #### 7. **选择建议** - 需要排序 → **TreeSet** - 追求最高性能且无需顺序 → **HashSet** - 需保留插入顺序且快速访问 → **LinkedHashSet** ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

BaymaxCS

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

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

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

打赏作者

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

抵扣说明:

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

余额充值