就是下面这段代码,把我给坑惨了:
public static <T> List<T> distinct(List<T> list) {
if (list == null) {
return null;
} else {
List<T> result = new ArrayList<>();
for (T item : list) {
if (!result.contains(item)) {
result.add(item);
}
}
return result;
}
}
功能很简单:集合去重,就是这段代码导致系统宕机,服务雪崩,导致扣季度奖没了。
这段代码存在什么问题呢?
可以用下面代码统计一下耗时,count表示集合数据量,可以测试一下count为1万、10万、100万的时候,耗时多少
public static void main(String[] args) {
//模拟集合数据量
int count = 10000;
List<Integer> list = new ArrayList<>();
for (Integer i = 0; i < count; i++) {
list.add(i);
}
long startime = System.currentTimeMillis();
List<Integer> distinct = distinct(list);
System.out.println(count + "数据量,耗时(毫秒):" + (System.currentTimeMillis() - startime));
}
耗时如下
count | 耗时 |
---|---|
10000 | 47毫秒 |
100000 | 3686毫秒 |
100万 | 6分钟 |
当100万数据的时候,耗时了6分钟,导致处理业务的线程被耗光了,导致大量请求处于等待中,最终导致系统服务雪崩,无法对外提供服务,造成了重大bug。
为什么会出现这样的结果?
我们再回头来看看去重的逻辑:
List<T> result = new ArrayList<>();
for (T item : list) {
if (!result.contains(item)) {
result.add(item);
}
}
这里调用了ArrayList中的contains方法判断result中是否存在item,不存在的时候,将item丢到result集合中,问题就出在了contains这个方法上,ArrayList内部是通过数组来存储数据的,判断原始是否在数组中存在,需要对数组进行遍历,只能按顺序遍历,我们我们看一下遍历的过程中contains内部需要遍历数组多少次
第几次执行result.contains | contains时result内部数组元素个数 | contains内部需要遍历数组的次数 | add后result内部数组元素个数 |
---|---|---|---|
1 | 0 | 0 | 1 |
2 | 1 | 1 | 2 |
3 | 2 | 2 | 3 |
… | …. | …. | …. |
n | n-1 | n-1 | n |
从上面可以推断出n个数字遍历次数 = n * n / 2,也就是n的平方除以2
n的值 | 遍历次数 |
---|---|
1万 | 5000万 |
10万 | 50亿次 |
100万 | 5000亿次 |
这段代码的根本问题,是没有掌握好ArrayList内部原理,导致了惨重的后果。
下面是优化之后的代码,测试了一下,100万数据,只需要60毫秒,下面这段为什么性能这么高?
public static <T> List<T> distinct(List<T> list) {
if (list == null) {
return null;
} else {
List<T> result = new ArrayList<>(new HashSet<>(list));
return result;
}
}
转载请注明:XAMPP中文组官网 » 这段java代码导致系统雪崩,让我损失了10万季度奖