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

我们实现 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 。

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

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

BaymaxCS

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

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

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

打赏作者

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

抵扣说明:

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

余额充值