从两个集合里面找出不同的元素不难,但是每个写法及实现的性能不一样,数量小的时候耗时差别不大,但数据量大的时候差别就十分明显了,如何解决优化?这就是算法的重要性了。本文以两个非空且自身无重复元素的两个集合举例。
方法一:使用hashMap
/**
* 找出两个list的不同元素,使用hashMap高效特性,数据量越大性能优势体现越为明显
* @param list1
* @param list2
* @return
*/
public static List<String> getDiffByHashMap(List<String> list1,List<String> list2){
long start = System.currentTimeMillis();
List<String> resultList = new ArrayList<String>();
//为了防止map扩容增加性能代价;初始化长度为两个链表的长度
int capacity = (int)((float)(list1.size()+list2.size())/0.75F+1.0F);
Map<String,Integer> map = new HashMap<>(capacity);
for (String s:list1) {
map.put(s,1);
}
for (String s:list2) {
if(map.get(s) == null){
map.put(s,1);
}else {
map.put(s,map.get(s)+1);
}
}
for (Map.Entry m :map.entrySet()) {
//value 大于1说明元素是两个list共有
if((int)m.getValue() == 1){
resultList.add((String)m.getKey());
}
}
long end = System.currentTimeMillis();
System.out.println("getDiffByHashMap耗时:"+(end-start));
return resultList;
}
方法二:使用HashSet
/**
* 找出两个list的不同元素,使用HashSet,数据量越大性能优势体现越为明显,亲测和使用hashmap时不分上下,甚至更快
* @param list1
* @param list2
* @return
*/
private static List<String> getDiffByHashSet(List<String> list1,List<String> list2){
long start = System.currentTimeMillis();
//temp存放两个list的共有元素
List<String> temp = new ArrayList<>();
//hashSet底层通过hashmpa实现的
Set<String> set = new HashSet<>((int)((float)(list1.size()+list2.size())/0.75F+1.0F));
for (String s1:list1) {
set.add(s1);
}
for (String s2:list2) {
//添加不成功说明元素共有
if(!set.add(s2)){
temp.add(s2);
}
set.add(s2);
}
//删除set中存在temp的元素
set.removeAll(temp);
long end = System.currentTimeMillis();
System.out.println("getDiffByHashSet耗时:"+(end-start));
return new ArrayList<>(set);
}
方法三:使用集合原生方法和遍历
/**
* 找出两个list的不同元素,使用原生方法,数据量越大性能越低下
* @param list1
* @param list2
* @return
*/
private static List<String> getDiffByNative1(List<String> list1,List<String> list2){
long start = System.currentTimeMillis();
List<String> resultList = new ArrayList<>();
for (String s1 :list1) {
if(!list2.contains(s1)){
resultList.add(s1);
}
}
for (String s2:list2) {
if(!list1.contains(s2)){
resultList.add(s2);
}
}
long end = System.currentTimeMillis();
System.out.println("getDiffByNative1耗时:"+(end-start));
return resultList;
}
方法四:使用集合的removeAll方法
/**
* 找出两个list的不同元素,使用原生方法removeAll,数据量越大越慢
* @param list1
* @param list2
* @return
*/
private static List<String> getDiffByNative2(List<String> list1,List<String> list2){
long start = System.currentTimeMillis();
List<String> resultList = new ArrayList<>();
//temp临时存放其中一个list的值
List<String> temp = new ArrayList<>();
temp.addAll(list1);
//删除list1存在list2的元素
list1.removeAll(list2);
//删除list2存在list的元素
list2.removeAll(temp);
resultList.addAll(list1);
resultList.addAll(list2);
long end = System.currentTimeMillis();
System.out.println("getDiffByNative2耗时:"+(end-start));
return resultList;
}
最后测试一下
随机生成字符串RandomUtils工具类
package com.example.algorithm.list;
import java.util.Random;
/**
* Copyright (C), 2018-2020
* FileName: AliPayConstant
* Date: 2020-08-07 17:18
* Description: 随机数工具类
*/
public class RandomUtils {
private RandomUtils() {
throw new RuntimeException("new RandomUtil instance error");
}
/**
* 获取随机验证码
* @param codeArray:随机验证码数组
* @param length:随机验证码长度
*/
public static String getRandomCode(char[] codeArray, int length) {
StringBuilder code = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i++) {
//random.nextInt该方法的作用是生成一个随机的int值,该值介于[0,n)的区间,也就是0到n之间的随机int值,包含0而不包含n。
code.append(codeArray[random.nextInt(codeArray.length)]);
}
return code.toString();
}
/**
* 生成指定长度的纯字母验证码
* @param length 验证码长度
*/
public static String getRandomWordCode(int length) {
char[] codeArray = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
return getRandomCode(codeArray, length);
}
}
开始测试,往集合中放入10000个字符串
/**
* 随机生成长度为10000,元素类型为String类型,字符串长度为5的Set
* @param set
*/
private static void addForList(Set<String> set){
for(int i = 0; i < 10000; i++){
set.add(RandomUtils.getRandomWordCode(5));
}
}
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
Set<String> set2 = new HashSet<>();
//为测试结果更明显,手动创造两个列表有重复元素
set1.add("ff");set2.add("ff");
addForList(set1);addForList(set2);
List<String> list1 = new ArrayList<>(set1);
List<String> list2 = new ArrayList<>(set2);
System.out.println("-----------------");
System.out.println("list1="+list1);
System.out.println("list2="+list2);
System.out.println("list长度:"+list1.size()+";"+"list2长度:"+list2.size());
List<String> result1 = getDiffByHashMap(list1,list2);
List<String> result2 = getDiffByHashSet(list1,list2);
List<String> result3 = getDiffByNative1(list1,list2);
List<String> result4 = getDiffByNative2(list1,list2);
System.out.println("getDiffByHashMap结果集长度:"+result1.size());
System.out.println("getDiffByHashSet结果集长度:"+result2.size());
System.out.println("getDiffByNative1结果集长度:"+result3.size());
System.out.println("getDiffByNative2结果集长度:"+result4.size());
System.out.println("-----------------");
System.out.println("getDiffByHashMap结果集"+result1);
System.out.println("getDiffByHashSet结果集"+result2);
System.out.println("getDiffByNative1结果集"+result3);
System.out.println("getDiffByNative2结果集"+result4);
}
测试结果
总结:经过多次测试发现,数据量越大的情况下,他们的性能从好到差的排列是:方法2>方法1>方法4>方法3;若数据量过少则差别很小。