我们实现 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 |
---|---|---|---|
添加 | 高 | 低 | 中 |
查询 | 中 | 低 | 高 |
移除 | 中 | 低 | 高 |
遍历 | 中 | 低 | 高 |
小结
- HashSet 和 LinkedHashSet 在各方面的性能都优于 TreeSet ,但 TreeSet 内部支持排序,当你需要一个排序好的 Set 时,推荐使用 TreeSet。
- LinkedHashSet 保证了元素的插入顺序,在查询、移除和遍历方面性能优于 HashSet ,但因其底层数据结构为链表,在执行插入操作的时候维护成本较高,故性能不及 HashSet 。
本次分享至此结束,希望本文对你有所帮助,若能点亮下方的点赞按钮,在下感激不尽,谢谢您的【精神支持】。
若有任何疑问,也欢迎与我交流,若存在不足之处,也欢迎各位指正!