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.测试结果汇总

测试项目 HashSet TreeSet LinkedHashSet
添加
查询
移除
遍历

小结

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

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

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

猜你喜欢

转载自blog.csdn.net/BaymaxCS/article/details/121319207
71