最近做的一個項目,獲取檢查兩個request中的選項是否一樣,所以碰到了校驗兩個List是否相等的問題,所以在此yy了一下。
我們看看如何比較兩個數組相等。數組是一個連續的內存空間,所以一般來說,兩個數組相等,就是意味着他們有相同的長度,相同的元素,以及相同的順序。我們看看JDK的Arrays.equals()實現就一目瞭然了。
public static boolean equals(int[] a, int[] a2) {
if (a==a2) return true;
if (a==null || a2==null) return false;
int length = a.length;
if (a2.length != length) return false;
for (int i=0; i<length; i++)
if (a[i] != a2[i]) return false;
return true;
}
大致分成下面4個步驟:
1. 檢查是否指向同一個地址(不同引用);
2. 非空檢查;
3. 長度檢查;
4. 順序和對應的元素相等檢查。(依次比較,只要一個不等就返回false)
而對於List來說,沒有固定的順序(ArrayList底層是數組,可以用數組的比較方法;但是其他的List,如LinkedList,就不一樣了。),或者說順序對List來說不是一個重要的屬性。所以可以考慮其他方式來檢查兩個List是不是包含相同的元素。
如JDK的Collection.containsAll(),只是是對每個元素進行contains()操作。但是List允許重複的元素,所以這裏無法比較重複元素的數量:
public boolean containsAll(Collection<?> c) {
for (Object e : c)
if (!contains(e))
return false;
return true;
}
那麼,如何對無序的元素進行equal比較呢?
比較簡便直觀的方法,就是分別對兩個list排序,然後一個一個比較,只要不相等,則返回false,如果全部都相等,則返回true。
if (a.size() !=b.size())
return false;
Collections.sort(a);
Collections.sort(b);
for (int i = 0; i <a.size(); i++) {
if(!a.get(i).equals(b.get(i)))
return false;
}
return true;
但是這樣兩次排序,效率還是比較低的。所以可以通過Map來實現:
先存入HashMap,key爲元素,value爲出現的次數,然後再逐個元素比較出現的次數,這樣能確保元素都包含,而且出現次數相同。
如下代碼所示,具體可以參考org.apache.commons.collections.CollectionUtils.isEqualCollection() 。(CollectionUtils中大量使用了getCardinalityMap()的特性)
private static final Integer INTEGER_ONE = 1;
public static boolean isEqualCollection(Collection a, Collection b){
if (a.size() !=b.size()) { // size是最簡單的相等條件
return false;
}
Map mapa = getCardinalityMap(a);
Map mapb = getCardinalityMap(b);
// 轉換map後,能去掉重複的,這時候size就是非重複項,也是先決條件
if (mapa.size() !=mapb.size()) {
return false;
}
Iterator it =mapa.keySet().iterator();
while (it.hasNext()) {
Object obj = it.next();
// 查詢同一個obj,首先兩邊都要有,而且還要校驗重複個數,就是map.value
if (getFreq(obj,mapa) != getFreq(obj, mapb)) {
return false;
}
}
return true;
}
/**
* 以obj爲key,可以防止重複,如果重複就value++
* 這樣實際上記錄了元素以及出現的次數
*/
public static Map getCardinalityMap(Collection coll) {
Map count = new HashMap();
for (Iterator it =coll.iterator(); it.hasNext();) {
Object obj =it.next();
Integer c =(Integer) count.get(obj);
if (c == null)
count.put(obj, INTEGER_ONE);
else {
count.put(obj, newInteger(c.intValue() + 1));
}
}
return count;
}
private static final int getFreq(Objectobj, Map freqMap) {
Integer count =(Integer) freqMap.get(obj);
if (count != null) {
return count.intValue();
}
return 0;
}