大數(shù)組元素差異removeAll與Map效率對(duì)比
正文
考慮這樣一個(gè)場(chǎng)景,對(duì)兩個(gè)列表對(duì)象,listA 和 listB,比較二者差異,找出只在 listA 中出現(xiàn)的元素列表 onlyListA,找出只在 listB 中出現(xiàn)的元素列表 onlyListB。
removeAll實(shí)現(xiàn)
很容易想到借助 removeAll 實(shí)現(xiàn),代碼如下。
List<String> listA = new ArrayList<>(); List<String> listB = new ArrayList<>(); //僅在數(shù)組A中出現(xiàn)的元素 List<String> onlyListA = new ArrayList<>(listA); onlyListA.removeAll(listB); //僅在數(shù)組B中出現(xiàn)的元素 List<String> onlyListB = new ArrayList<>(listB); onlyListB.removeAll(listA);
當(dāng)數(shù)組元素較少時(shí),借助 removeAll 實(shí)現(xiàn)并沒有任何問題。不過在數(shù)組元素較大時(shí),removeAll 方法耗時(shí)會(huì)較大。執(zhí)行如下測(cè)試方法,對(duì)數(shù)組元素個(gè)數(shù)為1000,1W,10W,100W 的場(chǎng)景進(jìn)行測(cè)試。
public class ListDiffTest {
public static void main(String[] args) {
testRemoveAllCostTime(1000);
testRemoveAllCostTime(10000);
testRemoveAllCostTime(100000);
testRemoveAllCostTime(1000000);
}
public static void testRemoveAllCostTime(int size) {
List<String> listA = dataList(size);
listA.add("onlyAElement");
List<String> listB = dataList(size + 3);
long startTime = System.currentTimeMillis();
//僅在數(shù)組A中出現(xiàn)的元素
List<String> onlyListA = new ArrayList<>(listA);
onlyListA.removeAll(listB);
//僅在數(shù)組B中出現(xiàn)的元素
List<String> onlyListB = new ArrayList<>(listB);
onlyListB.removeAll(listA);
System.out.println("僅在集合A中出現(xiàn)的元素:" + onlyListA);
System.out.println("僅在集合B中出現(xiàn)的元素:" + onlyListB);
System.out.println("元素個(gè)數(shù) = " + size + "時(shí),比對(duì)耗時(shí):" + (System.currentTimeMillis() - startTime) + " 毫秒");
}
private static List<String> dataList(int size) {
List<String> dataList = new ArrayList<>();
for (int i = 0; i < size; i++) {
dataList.add("" + i);
}
return dataList;
}
}
測(cè)試結(jié)果如下
僅在集合A中出現(xiàn)的元素:[onlyAElement]
僅在集合B中出現(xiàn)的元素:[1000, 1001, 1002]
元素個(gè)數(shù) = 1000時(shí),比對(duì)耗時(shí):19 毫秒
元素個(gè)數(shù) = 10000時(shí),比對(duì)耗時(shí):299 毫秒 #1W
元素個(gè)數(shù) = 100000時(shí),比對(duì)耗時(shí):24848 毫秒 #10W
元素個(gè)數(shù) = 1000000時(shí),比對(duì)耗時(shí):3607607 毫秒 #100W 約60m
可以看到,當(dāng)數(shù)組元素達(dá)到百萬級(jí)時(shí),耗時(shí)將達(dá)60min上下。
借助Map實(shí)現(xiàn)
此處給出一種優(yōu)化方式,借助 Map 計(jì)數(shù),將 List 集合中的元素作為 Map 的 key,元素出現(xiàn)的次數(shù)作為 Map 的 value。代碼實(shí)現(xiàn)如下。
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<String> listA = dataList(size);
listA.add("onlyAElement");
List<String> listB = dataList(size + 3);
long startTime = System.currentTimeMillis();
//僅在數(shù)組A中出現(xiàn)的元素
List<String> onlyListA = tuple2._1;;
//僅在數(shù)組B中出現(xiàn)的元素
List<String> onlyListB = tuple2._2;
System.out.println("僅在集合A中出現(xiàn)的元素:" + onlyListA);
System.out.println("僅在集合B中出現(xiàn)的元素:" + onlyListB);
System.out.println("元素個(gè)數(shù) = " + size + "時(shí),比對(duì)耗時(shí):" + (System.currentTimeMillis() - startTime) + " 毫秒");
}
/**
* 通過Map計(jì)數(shù)方式 比較兩個(gè)數(shù)組之間的差異
*
* @param listA 數(shù)組A
* @param listB 數(shù)組B
* @param <E> 元素類型
* @return Tuple2對(duì)象 onlyAList-只在數(shù)組A存在的元素 onlyBList-只在數(shù)組B存在的元素
*/
public static <E> Tuple2<List<E>, List<E>> getDiffListBtMapCompare(List<E> listA, List<E> listB) {
ValidateUtils.validateNotNull(listA, "listA");
ValidateUtils.validateNotNull(listB, "listB");
List<E> onlyAList = new ArrayList<>();
List<E> onlyBList = new ArrayList<>();
if (CollectionUtils.isEmpty(listA)) {
return Tuple.of(onlyAList, listB);
} else if (CollectionUtils.isEmpty(listB)) {
return Tuple.of(listA, onlyBList);
}
/**
* listA中元素 初始化計(jì)數(shù) = 1
* listB中元素 初始化計(jì)數(shù) = -2
* 遍歷累加后
* 相同元素 計(jì)數(shù) = 2
* 僅A中出現(xiàn)元素 計(jì)數(shù) = 1
* 僅A中出現(xiàn)元素 計(jì)數(shù) = -1
*/
Map<E, Integer> 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);
}
}
測(cè)試結(jié)果如下
僅在集合A中出現(xiàn)的元素:[onlyAElement]
僅在集合B中出現(xiàn)的元素:[1000, 1002, 1001]
元素個(gè)數(shù) = 1000時(shí),比對(duì)耗時(shí):8 毫秒
元素個(gè)數(shù) = 10000時(shí),比對(duì)耗時(shí):19 毫秒 #1W
元素個(gè)數(shù) = 100000時(shí),比對(duì)耗時(shí):28 毫秒 #10W
元素個(gè)數(shù) = 1000000時(shí),比對(duì)耗時(shí):96 毫秒 #100W
元素個(gè)數(shù) = 10000000時(shí),比對(duì)耗時(shí):5320 毫秒 #1000W
removeAll耗時(shí)分析
最后,來分析下為什么在大數(shù)組元素比較時(shí),removeAll 性能較差。
removeAll方法中,先進(jìn)行判空,然后調(diào)用batchRemove()方法
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
batchRemove()方法中,使用 for 循環(huán)對(duì)集合進(jìn)行遍歷。第 1 層循環(huán)需要執(zhí)行listA.size()次。循環(huán)體中調(diào)用了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()方法的實(shí)現(xiàn)如下,內(nèi)部又調(diào)用了indexOf()方法。indexOf()方法內(nèi)部又進(jìn)行了一層 for 循環(huán)遍歷。
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;
}
- 至此,可以看到,按照平均每次遍歷要進(jìn)行
list.size() / 2次計(jì)算,假設(shè)集合 A 的元素個(gè)數(shù)為 m,集合 B 的元素個(gè)數(shù)為 n,則兩重 for 循環(huán)下,會(huì)執(zhí)行m*n/2次。對(duì)于兩個(gè)千萬量級(jí)的數(shù)組,將執(zhí)行 100 億次計(jì)算!??!
由此給出一個(gè)結(jié)論,對(duì)于大數(shù)組元素差異比較,不建議使用 removeAll,可以借助 Map 實(shí)現(xiàn)。
參考 http://chabaoo.cn/article/261737.htm
以上就是大數(shù)組元素差異removeAll與Map效率對(duì)比的詳細(xì)內(nèi)容,更多關(guān)于removeAll Map效率對(duì)比的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
舉例講解Java的Hibernate框架中的多對(duì)一和一對(duì)多映射
這篇文章主要介紹了Java的Hibernate框架中的多對(duì)一和一對(duì)多映射,Hibernate是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-12-12
SpringBoot項(xiàng)目創(chuàng)建使用+配置文件+日志文件詳解
Spring的出現(xiàn)是為了簡(jiǎn)化 Java 程序開發(fā),而 SpringBoot 的出現(xiàn)是為了簡(jiǎn)化 Spring 程序開發(fā),這篇文章主要介紹了SpringBoot項(xiàng)目創(chuàng)建使用+配置文件+日志文件,需要的朋友可以參考下2023-02-02
Java源碼解析之Gateway請(qǐng)求轉(zhuǎn)發(fā)
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識(shí),文章圍繞著Gateway請(qǐng)求轉(zhuǎn)發(fā)展開,文中有非常詳細(xì)介紹及代碼示例,需要的朋友可以參考下2021-06-06
手把手帶你掌握SpringBoot RabbitMQ延遲隊(duì)列
RabbitMQ 是一個(gè)由Erlang語言開發(fā)的AMQP的開源實(shí)現(xiàn),支持多種客戶端。用于在分布式系統(tǒng)中存儲(chǔ)轉(zhuǎn)發(fā)消息,在易用性、擴(kuò)展性、高可用性等方面表現(xiàn)不俗,下文將帶你深入了解 RabbitMQ 延遲隊(duì)列2021-09-09
MyBatisPuls多數(shù)據(jù)源操作數(shù)據(jù)源偶爾報(bào)錯(cuò)問題
這篇文章主要介紹了MyBatisPuls多數(shù)據(jù)源操作數(shù)據(jù)源偶爾報(bào)錯(cuò)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
java數(shù)據(jù)類型與二進(jìn)制詳細(xì)介紹
這篇文章主要介紹了java數(shù)據(jù)類型與二進(jìn)制詳細(xì)介紹的相關(guān)資料,這里對(duì)數(shù)據(jù)類型進(jìn)行了一一介紹分析,并說明自動(dòng)轉(zhuǎn)換和強(qiáng)制轉(zhuǎn)換,需要的朋友可以參考下2017-07-07
使用Java實(shí)現(xiàn)一個(gè)能保留計(jì)算過程的計(jì)算器
計(jì)算器是我們?nèi)粘I钪谐S玫墓ぞ咧?它能夠進(jìn)行基本的數(shù)學(xué)運(yùn)算,如加法、減法、乘法和除法,而在設(shè)計(jì)一個(gè)計(jì)算器時(shí),我們可以通過使用Java編程語言來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的控制臺(tái)計(jì)算器,并且讓它能夠保留計(jì)算過程,文中有詳細(xì)的代碼示例,需要的朋友可以參考下2023-11-11
spring-kafka使消費(fèi)者動(dòng)態(tài)訂閱新增的topic問題
這篇文章主要介紹了spring-kafka使消費(fèi)者動(dòng)態(tài)訂閱新增的topic問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12

