1. 程式人生 > >百萬數量級的2個集合差異性對比的思考

百萬數量級的2個集合差異性對比的思考

  最近在專案中遇到這樣一個問題,對百萬級的資料進行一個比對,大致有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次數也更少。