考虑这样一个场景,对两个列表对象,listA 和 listB,比较二者差异,找出只在 listA 中出现的元素列表 onlyListA,找出只在 listB 中出现的元素列表 onlyListB。
很容易想到借助 removeAll 实现,代码如下。
List listA = new ArrayList<>();
List listB = new ArrayList<>();
//仅在数组A中出现的元素
List onlyListA = new ArrayList<>(listA);
onlyListA.removeAll(listB);
//仅在数组B中出现的元素
List onlyListB = new ArrayList<>(listB);
onlyListB.removeAll(listA);
当数组元素较少时,借助 removeAll 实现并没有任何问题。不过在数组元素较大时,removeAll 方法耗时会较大。执行如下测试方法,对数组元素个数为1000,1W,10W,100W 的场景进行测试。
public class ListDiffTest {
public static void main(String[] args) {
testRemoveAllCostTime(1000);
testRemoveAllCostTime(10000);
testRemoveAllCostTime(100000);
testRemoveAllCostTime(1000000);
}
public static void testRemoveAllCostTime(int size) {
List listA = dataList(size);
listA.add("onlyAElement");
List listB = dataList(size + 3);
long startTime = System.currentTimeMillis();
//仅在数组A中出现的元素
List onlyListA = new ArrayList<>(listA);
onlyListA.removeAll(listB);
//仅在数组B中出现的元素
List onlyListB = new ArrayList<>(listB);
onlyListB.removeAll(listA);
System.out.println("仅在集合A中出现的元素:" + onlyListA);
System.out.println("仅在集合B中出现的元素:" + onlyListB);
System.out.println("元素个数 = " + size + "时,比对耗时:" + (System.currentTimeMillis() - startTime) + " 毫秒");
}
private static List dataList(int size) {
List dataList = new ArrayList<>();
for (int i = 0; i < size; i++) {
dataList.add("" + i);
}
return dataList;
}
}
测试结果如下
仅在集合A中出现的元素:[onlyAElement]
仅在集合B中出现的元素:[1000, 1001, 1002]
元素个数 = 1000时,比对耗时:19 毫秒
元素个数 = 10000时,比对耗时:299 毫秒 #1W
元素个数 = 100000时,比对耗时:24848 毫秒 #10W
元素个数 = 1000000时,比对耗时:3607607 毫秒 #100W 约60m
可以看到,当数组元素达到百万级时,耗时将达60min上下。
此处给出一种优化方式,借助 Map 计数,将 List 集合中的元素作为 Map 的 key,元素出现的次数作为 Map 的 value。代码实现如下。
import io.vavr.Tuple2;
public class ListDiffTest {
public static void main(String[] args) {
testDifferListByMapCostTime(1000);
testDifferListByMapCostTime(10000);
testDifferListByMapCostTime(100000);
testDifferListByMapCostTime(1000000);
}
public static void testDifferListByMapCostTime(int size) {
List listA = dataList(size);
listA.add("onlyAElement");
List listB = dataList(size + 3);
long startTime = System.currentTimeMillis();
//仅在数组A中出现的元素
List onlyListA = tuple2._1;;
//仅在数组B中出现的元素
List onlyListB = tuple2._2;
System.out.println("仅在集合A中出现的元素:" + onlyListA);
System.out.println("仅在集合B中出现的元素:" + onlyListB);
System.out.println("元素个数 = " + size + "时,比对耗时:" + (System.currentTimeMillis() - startTime) + " 毫秒");
}
/**
* 通过Map计数方式 比较两个数组之间的差异
*
* @param listA 数组A
* @param listB 数组B
* @param 元素类型
* @return Tuple2对象 onlyAList-只在数组A存在的元素 onlyBList-只在数组B存在的元素
*/
public static Tuple2, List> getDiffListBtMapCompare(List listA, List listB) {
ValidateUtils.validateNotNull(listA, "listA");
ValidateUtils.validateNotNull(listB, "listB");
List onlyAList = new ArrayList<>();
List onlyBList = new ArrayList<>();
if (CollectionUtils.isEmpty(listA)) {
return Tuple.of(onlyAList, listB);
} else if (CollectionUtils.isEmpty(listB)) {
return Tuple.of(listA, onlyBList);
}
/**
* listA中元素 初始化计数 = 1
* listB中元素 初始化计数 = -2
* 遍历累加后
* 相同元素 计数 = 2
* 仅A中出现元素 计数 = 1
* 仅A中出现元素 计数 = -1
*/
Map countMap = new HashMap<>(Math.max(listA.size(), listB.size()));
for (E eleA : listA) {
countMap.put(eleA, 1);
}
for (E eleB : listB) {
countMap.put(eleB, 1 + countMap.getOrDefault(eleB, -2));
}
countMap.forEach((k, v) -> {
//获取不同元素集合
if (v == 1) {
onlyAList.add(k);
} else if (v == -1) {
onlyBList.add(k);
}
});
return Tuple.of(onlyAList, onlyBList);
}
}
测试结果如下
仅在集合A中出现的元素:[onlyAElement]
仅在集合B中出现的元素:[1000, 1002, 1001]
元素个数 = 1000时,比对耗时:8 毫秒
元素个数 = 10000时,比对耗时:19 毫秒 #1W
元素个数 = 100000时,比对耗时:28 毫秒 #10W
元素个数 = 1000000时,比对耗时:96 毫秒 #100W
元素个数 = 10000000时,比对耗时:5320 毫秒 #1000W
最后,来分析下为什么在大数组元素比较时,removeAll 性能较差。
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
由此给出一个结论,对于大数组元素差异比较,不建议使用 removeAll,可以借助 Map 实现。
页面更新:2024-02-15
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号