现有如下需求,需在订单信息类中根据用户信息类的
userId
匹配到对应的信息并输出商品skuId。
先给出实体类:用户信息类UserInfo
和订单信息类OrderInfo
。用户信息类如下所示:
/**
* @author Carson Chu
* @date 2020/1/13 19:43
* 用户信息类
*/
@Data
public class UserInfo {
/* 用户Id */
private String userId;
/* 用户名 */
private String name;
/* 用户性别 */
private String gender;
public UserInfo(String userId, String name, String gender) {
this.userId = userId;
this.name = name;
this.gender = gender;
}
}
订单信息类如下:
/**
* @author Carson Chu
* @date 2020/1/13 19:44
* 订单信息类
*/
@Data
public class OrderInfo {
/* 用户Id */
private String userId;
/* 订单号 */
private Long orderId;
/* 商品sku */
private Long skuId;
/* 商品价格 */
private BigDecimal price;
public OrderInfo(String userId, Long orderId, Long skuId, BigDecimal price) {
this.userId = userId;
this.orderId = orderId;
this.skuId = skuId;
this.price = price;
}
}
假设用户信息表和订单信息表中各有十万条数据,需要匹配到商品skuId,先采用双层for循环,附上main()方法逻辑:
/**
* @author Carson Chu
* @date 2020/1/13 20:09
*/
public class Main {
public static void main(String[] args) {
/* 用户信息集合 */
List<UserInfo> userInfoList = Lists.newArrayList();
/* 订单信息集合 */
List<OrderInfo> orderInfoList = Lists.newArrayList();
/* 手动向集合中添加十万条数据 */
for (int i = 0; i < 100000; i++) {
String userId = i + "";
String name = "用户" + i;
String gender = "female";
UserInfo userInfo = new UserInfo(userId, name, gender);
Long orderId = Long.valueOf(createNum(6));
Long skuId = Long.valueOf(createNum(8));
OrderInfo orderInfo = new OrderInfo(userId, orderId, skuId, new BigDecimal("66"));
userInfoList.add(userInfo);
orderInfoList.add(orderInfo);
}
long start = System.currentTimeMillis();
for (UserInfo userInfo : userInfoList) {
for (OrderInfo orderInfo : orderInfoList) {
if (userInfo.getUserId().equals(orderInfo.getUserId())) {
System.out.println(orderInfo.getSkuId());
}
}
}
long end = System.currentTimeMillis();
System.out.println("采用原始的双层for循环查询累计耗时:" + (end - start) + "毫秒");
}
/**
* 生成指定长度的随机数
*
* @param length
* @return
*/
public static String createNum(int length) {
StringBuilder sb = new StringBuilder();
Random rand = new Random();
for (int i = 0; i < length; i++) {
sb.append(rand.nextInt(10));
}
return sb.toString();
}
}
采用双层for循环的运行时间很长,将近4分钟,如下图所示:
如上图所示,查询消耗十分巨大,这种响应时间,玩笑开大了!这还不包括复杂业务场景下的I/O消耗等,可以说性能极其糟糕,线上环境绝对不允许如此缓慢的响应时间。双层for循环时间复杂度O(N2),效率极低,并且性能随着处理容量N呈急剧下降(开发中应该保证性能平缓下降)甚至将导致接口直接崩溃。那么,应该如何优化呢?
方案一:HashMap
将main()方法中的主要处理逻辑修改为采用哈希,使用两次单层循环:
long start = System.currentTimeMillis();
Map<String, Long> hashMap = new HashMap<>();
/* 先将所有的订单信息都放在HashMap,哈希的查找时间复杂度是O(1) */
for (OrderInfo orderInfo : orderInfoList) {
hashMap.put(orderInfo.getUserId(), orderInfo.getSkuId());
}
for (UserInfo userInfo : userInfoList) {
System.out.println(hashMap.get(userInfo.getUserId()));
}
long end = System.currentTimeMillis();
System.out.println("利用HashMap查询累计耗时:" + (end - start) + "毫秒");
如上图所示,哈希的查询十分的快,响应时间缩小到了915毫秒,为秒级,效率比双层for循环提高了两百多倍。
方案二:JDK8的lambda表达式和stream流
此外也可以采用jdk8的lambda表达式:
long start = System.currentTimeMillis();
// 将List转为Map,这里key一定要为唯一值
Map<String, UserInfo> userInfoMap = userInfoList.stream().collect(Collectors.toMap(userInfo -> userInfo.getUserId(), userInfo -> userInfo));
// 商品skuId集合
List<Long> skuIdList = Lists.newArrayList();
skuIdList = orderInfoList.stream().map(orderInfo -> {
return getSkuId(userInfoMap.get(orderInfo.getUserId()), orderInfo);
}).collect(Collectors.toList());
skuIdList.stream().forEach(skuId -> {
System.out.println("商品skuId:" + skuId);
});
long end = System.currentTimeMillis();
System.out.println("利用lambda表达式和stream流查询累计耗时:" + (end - start) + "毫秒");
附加匹配用户信息和订单信息的方法逻辑:
/**
* 匹配用户信息和订单信息,成功匹配后返回用户对应的商品skuId
*
* @param userInfo
* @param orderInfo
* @return
*/
private static Long getSkuId(UserInfo userInfo, OrderInfo orderInfo) {
if (orderInfo.getUserId().equals(userInfo.getUserId())) {
return orderInfo.getSkuId();
}
return null;
}
如上图所示,耗时也是毫秒级的,和哈希效率相当,因为事实上这种stream流就是基于HashMap
的。双层for循环和这两种方案相比,执行效率和用户体验上可谓是天差地别。