Java - List 的 contains 方法的性能
有一個需求,對一個List中的元素,獲取的所有Record字段,要求去重,並作爲List返回。現在有兩個方案,一個是使用ArrayList
(LinkedList
類似),另一個是使用HashSet
,ArrayList
使用其contains()
方法來去重,HashSet
調用add()
方法自然會去重。具體實現如下:
package com.example.collection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author youngbear
* @email [email protected]
* @date 2018/8/15 22:17
* @blog https://blog.csdn.net/next_second
* @github https://github.com/YoungBear
* @description ArrayList 的contains方法的性能問題
*/
public class ContainsTest {
public static final int COLUMNS = 30;
public static final int NUMBERS = 10000;
public static void main(String[] args) {
List<Pair> pairs = generateData();
System.out.println("pairs.size(): " + pairs.size());
long beginList = System.currentTimeMillis();
List<Long> recordUsingList = getRecordUsingList(pairs);
long endList = System.currentTimeMillis();
long beginSet = System.currentTimeMillis();
List<Long> recordUsingSet = getRecordUsingSet(pairs);
long endSet = System.currentTimeMillis();
System.out.println("list: " + (endList - beginList) + " ms" + ", size: " + recordUsingList.size());
System.out.println("set: " + (endSet - beginSet) + " ms" + ", size: " + recordUsingSet.size());
}
/**
* 生成測試數據
* @return
*/
static List<Pair> generateData() {
List<Pair> pairs = new ArrayList<Pair>();
for (int i = 0; i < NUMBERS; i++) {
for (int j = 0; j < COLUMNS; j++) {
Pair pair = new Pair();
pair.setRecord((long)i);
pair.setName("name: " + i + ", " + j);
pairs.add(pair);
}
}
return pairs;
}
/**
* 使用列表去重
* @param list
* @return
*/
static List<Long> getRecordUsingList(List<Pair> list) {
List<Long> results = new ArrayList<Long>();
for (Pair pair : list) {
if (!results.contains(pair.getRecord())) {
results.add(pair.getRecord());
}
}
return results;
}
/**
* 使用Set去重
* @param list
* @return
*/
static List<Long> getRecordUsingSet(List<Pair> list) {
Set<Long> set = new HashSet<Long>();
for (Pair pair : list) {
set.add(pair.getRecord());
}
List<Long> results = new ArrayList<Long>(set);
return results;
}
static class Pair{
private Long record;
private String name;
public Long getRecord() {
return record;
}
public void setRecord(Long record) {
this.record = record;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
輸出結果爲:
pairs.size(): 300000
list: 13908 ms, size: 10000
set: 31 ms, size: 10000
分析
List
由於數據中存在重複元素,所以使用contains()
方法,但是,ArrayList
的contains()
方法會調用其indexOf()
方法,在indexOf()
方法裏邊,有一個for循環,所以,ArrayList
的contains()
方法的時間複雜度是O(n*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;
}
HashSet
對於HashSet
,它的add()
方法會自動去重,它調用的是一個map
的put
方法,其時間複雜度是O(1)
。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
結論
所以,在大量的數據的時候,不要使用List
的contains()
方法,其效率很低,可以考慮使用Set
來實現。
參考
HashSet vs ArrayList contains performance