百萬數量級的2個集合差異性對比的思考
阿新 • • 發佈:2018-12-22
最近在專案中遇到這樣一個問題,對百萬級的資料進行一個比對,大致有2個思路,一,將2個集合排序,將物件中需的屬性取出拼接成字串,然後對憑藉的字串進行摘要,最後對比2個集合的摘要值,二、重寫物件中hash和equal,直接對比2個集合,比較其不同。對於2個方案,做了一些對比。對比的主要點集中在耗時,記憶體的佔用,gc回收的次數
一、兩種比較方式的程式碼實現
摘要比對,這裡採用的是sha-1的方式進行摘要
/** * 生成摘要 * @param content * @return */ public static String getMessageDigest(String content) { MessageDigest messageDigest; StringBuffer sb=new StringBuffer(); try { long now = System.currentTimeMillis(); messageDigest = MessageDigest.getInstance("SHA-1"); messageDigest.update(content.getBytes("utf-8")); byte[] hash = messageDigest.digest(); for(int i = 0; i < hash.length; i++ ){int v = hash[i] & 0xFF; if(v < 16) { sb.append("0"); } sb.append(Integer.toString(v,16).toUpperCase()); } } catch (NoSuchAlgorithmException e) { sb.append("生成摘要異常").append(e.getMessage()); e.printStackTrace(); }catch (UnsupportedEncodingException e) { sb.append("生成摘要異常").append(e.getMessage()); e.printStackTrace(); } return sb.toString(); }
全量比對,這裡若採用jdk自帶的幾種直接對集合操作的api的話,如removeall,containsAll等等,追蹤其原始碼,都是採用雙重for迴圈來實現比較的,時間複雜度均為O(N2),所以我們採用hashmap作為媒介,採取一種時間複雜度為0(N)的方式來比較,程式碼如下,
/** * 獲取兩個集合的不同元素 * @param collmax * @param collmin * @return */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static Collection getDiffent(Collection collmax,Collection collmin) { //使用LinkeList防止差異過大時,元素拷貝 Collection csReturn = new LinkedList(); Collection max = collmax; Collection min = collmin; //先比較大小,這樣會減少後續map的if判斷次數 if(collmax.size()<collmin.size()) { max = collmin; min = collmax; } //直接指定大小,防止再雜湊 Map<Object,Integer> map = new HashMap<Object,Integer>(max.size()); for (Object object : max) { map.put(object, 1); } for (Object object : min) { if(map.get(object)==null) { csReturn.add(object); }else{ map.put(object, 2); } } for (Map.Entry<Object, Integer> entry : map.entrySet()) { if(entry.getValue()==1) { csReturn.add(entry.getKey()); } } return csReturn; }
二、驗證,
上面介紹了兩種方式來比對list,下面來做個測試
package com.example.demo; import com.example.demo.util.CollectionUtil; import org.springframework.boot.SpringApplication; import org.springframework.util.StringUtils; import java.util.*; import java.util.stream.Collectors; /** * 描述: 測試2個大的集合資料的對比 * * @author liuyao * @create 2018-12-18 14:14 */ public class CompareTest { public static void main(String[] args) { List<String> list1 = createLiet(1000000); List<String> list2 = createLiet2(1000000); System.out.println("開始對比---------"); long nowTime2 = System.currentTimeMillis(); Collection aa = CollectionUtil.getDiffent(list1,list2); System.out.println(aa); System.out.println("全量比對耗時"+(System.currentTimeMillis()-nowTime2)); long nowTime1 = System.currentTimeMillis(); String list1Str = CollectionUtil.getMessageDigest(StringUtils.collectionToDelimitedString(list1, ",")); String list2Str = CollectionUtil.getMessageDigest(StringUtils.collectionToDelimitedString(list2,",")); if (list1Str.equals(list2Str)){ System.out.println("2list相同"); } System.out.println("摘要耗時"+(System.currentTimeMillis()-nowTime1)); } private static List<String> createLiet2(int count) { Set<String> set = new HashSet<>(count); for (int i=10;i<count+10;i++) { set.add(new String(i+"測試資料abc")); } return new ArrayList<>(set); } private static List<String> createLiet(int count) { Set<String> set = new HashSet<>(count); for (int i=0;i<count;i++) { set.add(new String(i+"測試資料abc")); } return new ArrayList<>(set); } }
由於資料量較大,gc回收也會影響較大的,加入jvm引數-XX:+PrintGCDetails以對比gc情況
執行結果如下
可以看出全量對比是比摘要對比要耗時的,但是我們再看下gc日誌的情況,在全量比對期間,發生了一次full gc,耗時683ms,full gc期間會暫停其他程序,故結果偏差較大,下面我們修改list大小,改為50萬,重新執行
多個樣本比對,下面是200萬資料量的
由此看出,全量比對較摘要比對效能更好,並且能得到2個集合具體的差異。gc次數也更少。