大数组元素差异removeAll与Map效率源码对比分析

其他教程   发布日期:2023年08月12日   浏览次数:437

本文小编为大家详细介绍“大数组元素差异removeAll与Map效率源码对比分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“大数组元素差异removeAll与Map效率源码对比分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

考虑这样一个场景,对两个列表对象,

  1. listA
  1. listB
,比较二者差异,找出只在
  1. listA
中出现的元素列表
  1. onlyListA
,找出只在
  1. listB
中出现的元素列表
  1. onlyListB

removeAll实现

很容易想到借助

  1. removeAll
实现,代码如下。
  1. List<String> listA = new ArrayList<>();
  2. List<String> listB = new ArrayList<>();
  3. //仅在数组A中出现的元素
  4. List<String> onlyListA = new ArrayList<>(listA);
  5. onlyListA.removeAll(listB);
  6. //仅在数组B中出现的元素
  7. List<String> onlyListB = new ArrayList<>(listB);
  8. onlyListB.removeAll(listA);

当数组元素较少时,借助

  1. removeAll
实现并没有任何问题。不过在数组元素较大时,
  1. removeAll
方法耗时会较大。执行如下测试方法,对数组元素个数为1000,1W,10W,100W 的场景进行测试。
  1. public class ListDiffTest {
  2. public static void main(String[] args) {
  3. testRemoveAllCostTime(1000);
  4. testRemoveAllCostTime(10000);
  5. testRemoveAllCostTime(100000);
  6. testRemoveAllCostTime(1000000);
  7. }
  8. public static void testRemoveAllCostTime(int size) {
  9. List<String> listA = dataList(size);
  10. listA.add("onlyAElement");
  11. List<String> listB = dataList(size + 3);
  12. long startTime = System.currentTimeMillis();
  13. //仅在数组A中出现的元素
  14. List<String> onlyListA = new ArrayList<>(listA);
  15. onlyListA.removeAll(listB);
  16. //仅在数组B中出现的元素
  17. List<String> onlyListB = new ArrayList<>(listB);
  18. onlyListB.removeAll(listA);
  19. System.out.println("仅在集合A中出现的元素:" + onlyListA);
  20. System.out.println("仅在集合B中出现的元素:" + onlyListB);
  21. System.out.println("元素个数 = " + size + "时,比对耗时:" + (System.currentTimeMillis() - startTime) + " 毫秒");
  22. }
  23. private static List<String> dataList(int size) {
  24. List<String> dataList = new ArrayList<>();
  25. for (int i = 0; i < size; i++) {
  26. dataList.add("" + i);
  27. }
  28. return dataList;
  29. }
  30. }

测试结果如下

仅在集合A中出现的元素:[onlyAElement]
仅在集合B中出现的元素:[1000, 1001, 1002]
元素个数 = 1000时,比对耗时:19 毫秒
元素个数 = 10000时,比对耗时:299 毫秒 #1W
元素个数 = 100000时,比对耗时:24848 毫秒 #10W
元素个数 = 1000000时,比对耗时:3607607 毫秒 #100W 约60m

可以看到,当数组元素达到百万级时,耗时将达60min上下。

借助Map实现

此处给出一种优化方式,借助

  1. Map
计数,将 List 集合中的元素作为 Map 的 key,元素出现的次数作为 Map 的 value。代码实现如下。
  1. import io.vavr.Tuple2;
  2. public class ListDiffTest {
  3. public static void main(String[] args) {
  4. testDifferListByMapCostTime(1000);
  5. testDifferListByMapCostTime(10000);
  6. testDifferListByMapCostTime(100000);
  7. testDifferListByMapCostTime(1000000);
  8. }
  9. public static void testDifferListByMapCostTime(int size) {
  10. List<String> listA = dataList(size);
  11. listA.add("onlyAElement");
  12. List<String> listB = dataList(size + 3);
  13. long startTime = System.currentTimeMillis();
  14. //仅在数组A中出现的元素
  15. List<String> onlyListA = tuple2._1;;
  16. //仅在数组B中出现的元素
  17. List<String> onlyListB = tuple2._2;
  18. System.out.println("仅在集合A中出现的元素:" + onlyListA);
  19. System.out.println("仅在集合B中出现的元素:" + onlyListB);
  20. System.out.println("元素个数 = " + size + "时,比对耗时:" + (System.currentTimeMillis() - startTime) + " 毫秒");
  21. }
  22. /**
  23. * 通过Map计数方式 比较两个数组之间的差异
  24. *
  25. * @param listA 数组A
  26. * @param listB 数组B
  27. * @param <E> 元素类型
  28. * @return Tuple2对象 onlyAList-只在数组A存在的元素 onlyBList-只在数组B存在的元素
  29. */
  30. public static <E> Tuple2<List<E>, List<E>> getDiffListBtMapCompare(List<E> listA, List<E> listB) {
  31. ValidateUtils.validateNotNull(listA, "listA");
  32. ValidateUtils.validateNotNull(listB, "listB");
  33. List<E> onlyAList = new ArrayList<>();
  34. List<E> onlyBList = new ArrayList<>();
  35. if (CollectionUtils.isEmpty(listA)) {
  36. return Tuple.of(onlyAList, listB);
  37. } else if (CollectionUtils.isEmpty(listB)) {
  38. return Tuple.of(listA, onlyBList);
  39. }
  40. /**
  41. * listA中元素 初始化计数 = 1
  42. * listB中元素 初始化计数 = -2
  43. * 遍历累加后
  44. * 相同元素 计数 = 2
  45. * 仅A中出现元素 计数 = 1
  46. * 仅A中出现元素 计数 = -1
  47. */
  48. Map<E, Integer> countMap = new HashMap<>(Math.max(listA.size(), listB.size()));
  49. for (E eleA : listA) {
  50. countMap.put(eleA, 1);
  51. }
  52. for (E eleB : listB) {
  53. countMap.put(eleB, 1 + countMap.getOrDefault(eleB, -2));
  54. }
  55. countMap.forEach((k, v) -> {
  56. //获取不同元素集合
  57. if (v == 1) {
  58. onlyAList.add(k);
  59. } else if (v == -1) {
  60. onlyBList.add(k);
  61. }
  62. });
  63. return Tuple.of(onlyAList, onlyBList);
  64. }
  65. }

测试结果如下

仅在集合A中出现的元素:[onlyAElement]
仅在集合B中出现的元素:[1000, 1002, 1001]
元素个数 = 1000时,比对耗时:8 毫秒
元素个数 = 10000时,比对耗时:19 毫秒 #1W
元素个数 = 100000时,比对耗时:28 毫秒 #10W
元素个数 = 1000000时,比对耗时:96 毫秒 #100W
元素个数 = 10000000时,比对耗时:5320 毫秒 #1000W

removeAll耗时分析

最后,来分析下为什么在大数组元素比较时,

  1. removeAll
性能较差。
    1. removeAll
    方法中,先进行判空,然后调用
    1. batchRemove()
    方法
  1. public boolean removeAll(Collection<?> c) {
  2. Objects.requireNonNull(c);
  3. return batchRemove(c, false);
  4. }
    1. batchRemove()
    方法中,使用 for 循环对集合进行遍历。第 1 层循环需要执行
    1. listA.size()
    次。循环体中调用了
    1. contains()
    方法来确定集合 B 是否含有该元素。
  1. private boolean batchRemove(Collection<?> c, boolean complement) {
  2. final Object[] elementData = this.elementData;
  3. int r = 0, w = 0;
  4. boolean modified = false;
  5. try {
  6. for (; r < size; r++)
  7. if (c.contains(elementData[r]) == complement)
  8. elementData[w++] = elementData[r];
  9. } finally {
  10. // Preserve behavioral compatibility with AbstractCollection,
  11. // even if c.contains() throws.
  12. if (r != size) {
  13. System.arraycopy(elementData, r,
  14. elementData, w,
  15. size - r);
  16. w += size - r;
  17. }
  18. if (w != size) {
  19. // clear to let GC do its work
  20. for (int i = w; i < size; i++)
  21. elementData[i] = null;
  22. modCount += size - w;
  23. size = w;
  24. modified = true;
  25. }
  26. }
  27. return modified;
  28. }
    1. contains()
    方法的实现如下,内部又调用了
    1. indexOf()
    方法。
    1. indexOf()
    方法内部又进行了一层 for 循环遍历。
  1. public boolean contains(Object o) {
  2. return indexOf(o) >= 0;
  3. }
  4. public int indexOf(Object o) {
  5. if (o == null) {
  6. for (int i = 0; i < size; i++)
  7. if (elementData[i]==null)
  8. return i;
  9. } else {
  10. for (int i = 0; i < size; i++)
  11. if (o.equals(elementData[i]))
  12. return i;
  13. }
  14. return -1;
  15. }
  • 至此,可以看到,按照平均每次遍历要进行

    1. list.size() / 2
    次计算,假设集合 A 的元素个数为 m,集合 B 的元素个数为 n,则两重 for 循环下,会执行
    1. m*n/2
    次。对于两个千万量级的数组,将执行 100 亿次计算!!!

由此给出一个结论,对于大数组元素差异比较,不建议使用

  1. removeAll
,可以借助 Map 实现。

以上就是大数组元素差异removeAll与Map效率源码对比分析的详细内容,更多关于大数组元素差异removeAll与Map效率源码对比分析的资料请关注九品源码其它相关文章!