大数组元素差异比较下的removeAll和Map效率对比
考虑这样一个场景,对两个列表对象,listA 和 listB,比较二者差异,找出只在 listA 中出现的元素列表 onlyListA,找出只在 listB 中出现的元素列表 onlyListB。removeAll实现
很容易想到借助 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实现
此处给出一种优化方式,借助 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 毫秒 #1000WremoveAll耗时分析
最后,来分析下为什么在大数组元素比较时,removeAll 性能较差。removeAll 方法中,先进行判空,然后调用 batchRemove() 方法 public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, false); }batchRemove() 方法中,使用 for 循环对集合进行遍历。第 1 层循环需要执行 listA.size() 次。循环体中调用了 contains() 方法来确定集合 B 是否含有该元素。 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; }contains() 方法的实现如下,内部又调用了 indexOf() 方法。indexOf() 方法内部又进行了一层 for 循环遍历。 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; }至此,可以看到,按照平均每次遍历要进行 list.size() / 2 次计算,假设集合 A 的元素个数为 m,集合 B 的元素个数为 n,则两重 for 循环下,会执行 m*n/2次。对于两个千万量级的数组,将执行 100 亿次计算!!!
由此给出一个结论,对于大数组元素差异比较,不建议使用 removeAll,可以借助 Map 实现。
尿道炎为淋病范畴,尿道炎的四种症型!尿道炎分为膀胱湿热型肝胆郁热型胃肠湿热型肾阴不足型1。膀胱湿热型膀胱湿热型主要表现发热恶寒尿频尿急尿痛小腹胀痛腰酸苔黄脉濡数治宜清热泻火通淋利水常用药大黄瞿麦萹蓄车前子木通山栀子甘
众人对颜丙涛的看法,墨菲逼其退役,小特佛系求情,丁俊晖太冷血北京时间12月15号,日前中国斯诺克七人正陷入赌球风波,丁俊晖近二十年职业生涯维持的正面形象很可能被梁文博和颜丙涛等人毁于一旦,这次丑闻让颜丙涛的职业生涯蒙上一层阴影,年仅22岁的
埃因青年VS阿贾青年世界杯已经进入了白热化的阶段,我们也不能错过一些小型的比赛,毕竟有些时候,小型的比赛会比世界杯更多。当然,今天我们要说的,就是荷乙两支超级豪门之间的对决,埃因霍温第二支对阵阿贾克斯
20岁小将横空出世!广东队锋线有救了,杜锋终于等到周鹏接班人广东队重返争冠行列第一阶段结束,广东队只排在联盟第11位,觉得这支球队真的到了一个转折点。进入到第二阶段,广东队势不可当,先后战胜北京广州天津辽宁,豪取四连胜,排名已经上升到了第五
瓦妮莎守寡失败?与光头男互送飞吻太暧昧,40岁单身富婆太抢手近日有不少球迷关注到了瓦妮莎和她的家人,瓦妮莎在前不久参与了许多活动,与此同时还和自己的大女儿多次拍摄写真,看起来非常的英姿飒爽,颇有女强人的风范,瓦妮莎之所以可以得到广泛关注,最
五位基金经理的投资干货!中欧基金年度策略会揭秘如何穿越周期长期制胜?投资唯有拥有全局视野,才能跳出只见树木不见森林的局限,洞察投资优秀企业的本质,把握中国乃至全球经济的发展趋势。即将过去的2022年是市场震荡加剧的一年,并没有出现趋势性机会,300
常见的三种补阳中成药,哪个效果更好?如果你平时总是觉得怕冷畏寒穿的厚也不暖和,天一凉就手脚冰凉,晚上睡觉时被窝怎么也暖不热,这多半是你阳虚了。那我们平时常见的桂附地黄丸金匮肾气丸和附子理中丸,哪个补阳效果更好呢?应该
小洋人日记第三天我快好了,儿子接上了,全盘端了今天我的症状就是咳嗽,但是咳嗽起来症状类似于普通感冒,嗓子不疼了。其他症状也消失了。但是,还没容我松一口气,昨天晚上我儿子就发烧了。这几天我羊了之后,为了不传染给孩子,我尽量和孩子
宝山疾控话健康到了冬天,气温骤降血管极易受到冷空气刺激而收缩造成血液流通受阻血液黏度增高使心脑负荷加重动脉粥样硬化斑块易破裂冠状动脉易痉挛进而诱发心脑血管疾病的发作和复发今天就让我们一起来了解下
咳嗽最怕这个汤!随手一煮,清热又润肺,尤其老人小孩要多喝现在正值深冬的季节,气候比较干燥,一到冬季,大家一般会懒于锻炼,抵抗力就容易下降,因此到了冬季总会觉得自己的喉咙发干发痒,偶尔就想咳嗽。如果情况再严重一些的,甚至还会感冒,所以我们
什么时候喝水对身体好喝水对身体好的时问有630喝水排毒叉养颜830喝水体贴又健康1100喝水解乏叉放松1250喝水减负又减肥1500喝水提神叉醒脑1730喝水消化又吸收2200喝水可解毒,排泄,消化增