list.contains() 的性能问题
0 业务场景
-
公司有一个前人写的业务接口耗时非常严重,200w 的数据进行查询,浏览器等待 1h 都响应不出结果,因此测试给我提了一个 bug,需要对代码性能进行优化。我对代码逻辑进行排查后,排除 SQL 原因,发现是 list.contains() 出现了阻塞。由于不能贴公司源代码,我对相关业务进行了一个抽象,做了一个类似的 demo
-
现在有 3 个 list 集合,User 有两个字段:username、password
-
List<User> all
存放的是所有人的数据 -
List<User> friends
存放的是好友的数据 -
List<User> contacts
存放的是相互联系的数据 -
需求:
1、从相互联系的数据过滤并收集好友的名称
2、从相互联系的数据过滤并收集非好友的名称
1 原始的代码逻辑
-
原始的,有性能问题的代码
// 好友
List<User> friend = contacts.stream()
.filter(item -> friends.contains(item.getUsername()))
.collect(Collectors.toList());
// 非好友
List<User> notFriend = contacts.stream()
.filter(item -> !friends.contains(item.getUsername()))
.collect(Collectors.toList());
-
以上代码逻辑在数据量较少时是没有问题的,但数据量在几十万的时候,代码出现了阻塞,一直出不来数据
2 原因分析
-
list.contains()
方法的性能问题主要归因于其内部实现。在 Java 的 ArrayList 类中,contains() 方法通过遍历列表中的每个元素并使用 equals() 方法进行比较来检查元素是否存在。这种线性搜索的时间复杂度为 O(n),其中 n 是列表中的元素数量。因此,当列表变得非常大时,性能会显著下降。 -
contains() 方法的源码如下:
int indexOfRange(Object o, int start, int end) {
Object[] es = elementData;
if (o == null) {
for (int i = start; i < end; i++) {
if (es[i] == null) {
return i;
}
}
} else {
for (int i = start; i < end; i++) {
if (o.equals(es[i])) {
return i;
}
}
}
return -1;
}
3 解决方案
-
方案1:使用 HashSet 或 HashMap,如果需要频繁地检查元素是否存在,且不关心元素的顺序,可以使用 HashSet 的 contains() 方法具有近似 O(1) 的时间复杂度,因为它基于哈希表实现。如果还需要与每个元素相关联的额外数据,可以使用 HashMap
-
方案2:对列表进行排序并使用二分搜索,如果列表是有序的,可以使用
Collections.binarySearch()
方法进行二分搜索。这会将时间复杂度降低到O(logn)。注意要求列表始终保持排序状态,这可能会增加插入和删除操作的开销 -
方案3:使用并行处理,如果数据集非常大,并且有多个处理器核心可用,你可以考虑使用 stream 流进行并行处理。但是,并行处理并不总是能提高性能,特别是在 I/O 受限或数据集不适合内存的情况下
-
方案4:自定义数据结构,实现自己的数据结构来优化性能。例如,可以使用布隆过滤器来快速检查元素可能不存在,然后再使用更精确的数据结构进行确认
-
方案5:缓存结果,如果数据集在一段时间内不会改变,或者如果相同的查询被频繁地执行,可以考虑缓存查询结果来减少不必要的计算
-
现在我使用方案1中的 HashMap 进行演示,完整代码如下:
-
User 类:
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Objects;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String username;
private String password;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(username, user.username) && Objects.equals(password, user.password);
}
@Override
public int hashCode() {
return Objects.hash(username, password);
}
}
-
测试类
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.example.demo.entity.User;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 测试list.contains()性能问题
*/
public class ContainsDemo {
public static void main(String[] args) {
// 好友列表
List<User> friendsData = friendsData();
// 聊天记录列表
List<User> contactsData = contactsData();
// 所有人的数据
List<User> allData = allData();
// key-用户名 value-User
Map<String, User> userMap = contactsData.stream().collect(Collectors.toMap(User::getUsername, o -> o, (v1, v2) -> v1));
long t1 = System.currentTimeMillis();
// 原逻辑-会产生性能问题
// List<User> friend = allData.stream().filter(item -> friends.contains(item.getUsername())).collect(Collectors.toList());
List<User> contactFriends = new ArrayList<>();
friendsData.forEach(item -> {
User user = userMap.get(item.getUsername());
if (!ObjectUtil.isNull(user)) {
contactFriends.add(new User(item.getUsername(), item.getPassword()));
}
});
System.out.println("耗时:" + (System.currentTimeMillis() - t1) + " 毫秒");
System.out.println("好友数:" + contactFriends.size());
// 所有人的数据中,移出所有的friends,剩下的都是非friends
allData.removeAll(contactFriends);
List<User> notContactFriends = new ArrayList<>(allData);
System.out.println("非好友数:" + notContactFriends.size());
}
/**
* 模拟数据库查询结果 - 所有人列表数据
*/
public static List<User> allData() {
List<User> allData = new ArrayList<>();
allData.addAll(initData());
allData.addAll(initData());
return allData;
}
/**
* 模拟数据库查询结果 - 好友列表数据
*/
public static List<User> friendsData() {
return initData();
}
/**
* 模拟数据库查询结果 - 聊天记录数据
*/
public static List<User> contactsData() {
return initData();
}
/**
* 模拟80w数据
*/
private static List<User> initData() {
List<User> initList = new ArrayList<>();
for (int i = 0; i < 800000; i++) {
StringBuilder sb = new StringBuilder();
char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
int length = 6;
for (int j = 0; j < length; j++) {
sb.append(chars[RandomUtil.randomInt(chars.length)]);
}
initList.add(new User(sb.toString(), sb.toString()));
}
return initList;
}
}
/**
* 输出结果:
* 耗时:283 毫秒
* 好友数:39
* 非好友数:1600000
*/
-
list.contains() 的性能问题分析就到此为止啦,如有错误,欢迎指正。
-
创作不易,感谢阅读,若遇到问题,可以关注此微信公众号留言反馈,希望能够帮助到您。
原文始发于微信公众号(EzCoding):list.contains() 方法,你真的用对了吗?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/203641.html