1. 程式人生 > >【策略與優化 - 001】- 在特定場景下,如何對雙層循環進行降級,加速數據匹配?

【策略與優化 - 001】- 在特定場景下,如何對雙層循環進行降級,加速數據匹配?

減少 cti val 是否 測試 map 大小 準備 獲取

一、場景介紹

假設某次搜索結果中有 100_0000 篇文章,而你的個人收藏中有 10000 篇,如何在短時間內快速識別 100_0000 中哪些是 “已收藏”, 哪些是 “未收藏” ?

二、正常邏輯(雙層for 循環)

public class ForEachTest {
    public static void main(String[] args) {
        // user book list
        List<String> ubList = new ArrayList<>();
        
        // book list
        List<String> bList = new
ArrayList<>(); // 收藏的數據條數 int collectionNum = 10000; // 總條數 int total = 100_0000; // 預計要存儲的結果,也可以定義在要返回到頁面實體的狀態中 Map<String, Integer> resMap = new HashMap<>(total); // 初始化 個人中心收藏的數據 for (int i = 0; i < collectionNum; i++) { ubList.add(String.valueOf(i)); }
// 初始化 搜索結果中返回的數據,同時維護一個 個人與文章 相關的狀態map // 狀態初始結果為 0:“未收藏”, 1:“已收藏” for (int i = 0; i < total; i++) { bList.add(String.valueOf(i)); //如果這個 bList 中存儲的是 實體對象,則可以在存入數據的時候,就初始化一個未收藏的狀態 resMap.put(String.valueOf(i), 0); } // 記錄開始時間
long start = System.currentTimeMillis(); /* for 雙層循環*/ for (String b : bList) { for (String ub : ubList) { if (b.equals(ub)) { resMap.put(b, 1); } } } // 結束時間 long end = System.currentTimeMillis(); // 用時時長 ms System.out.println("耗時 ms: " + (end - start)); } }

註: 在這幾次的測試中,都沒有涉及內存的消耗與數據準備的時間,具體查看時間計算的區間!

測試數據:

  collectionNum: 10000

  total : 100 0000

  消耗時間:

技術分享圖片

結論:雙層 for 循環,遍歷了 100億次,用時 62秒 左右

三、利用 HashMap 底層,減少無效的遍歷

public class ForEachTest {
    public static void main(String[] args) {
        // user book list
        List<String> ubList = new ArrayList<>();
        
        // book list
        List<String> bList = new ArrayList<>();
        
        // 收藏的數據條數
        int collectionNum = 10000;
        // 總條數
        int total = 100_0000;
        
        // 預計要存儲的結果,也可以定義在要返回到頁面實體的狀態中
        Map<String, Integer> resMap = new HashMap<>(total);
        
        // 初始化 個人中心收藏的數據
        for (int i = 0; i < collectionNum; i++) {
            ubList.add(String.valueOf(i));
        }
        
        // 初始化 搜索結果中返回的數據,同時維護一個 個人與文章 相關的狀態map
        // 狀態初始結果為 0:“未收藏”, 1:“已收藏”
        for (int i = 0; i < total; i++) {
            bList.add(String.valueOf(i));
            
            //如果這個 bList 中存儲的是 實體對象,則可以在存入數據的時候,就初始化一個未收藏的狀態
            resMap.put(String.valueOf(i), 0);
        }

        // 記錄開始時間
        long start = System.currentTimeMillis();

        // 將 個人中心收藏的數據,轉化存儲到 map 中,
        // 註意 收藏的文章的id 作為key,value 隨意,這裏使用同樣使用了 id
        Map<String, String> ubMap = new HashMap<>();
        for (String ubId : ubList) {
            ubMap.put(ubId, ubId);
        }

        // 開始遍歷 搜索結果中的 100萬條數據,是否有被個人收藏過的,有就改變返回的狀態。
        for (String bId : bList) {
            // 直接使用 ubMap 的查找key 值是否存在的方式,判斷該文章是否已經收藏。
            if (ubMap.containsKey(bId)){
                resMap.put(bId, 1);
            }
        }
        
        // 結束時間
        long end = System.currentTimeMillis();
        // 用時時長 ms
        System.out.println("耗時 ms: " + (end - start));
    }
}

測試數據:

  collectionNum: 10000

  total : 100 0000

  消耗時間:

技術分享圖片

結論:一層循環,加上內部的 hash 計算,用時 51ms

四、總結

條件: 兩組數據分別沒有重復的數據(id 或者 根據對比的字段不重復,也可以根據業務琢磨,即放在 map 中的 key 值不重復)

需求:對比一組數據中的數據,是否在另一組中有對應的匹配數據

結論使用 hashmap 的 key值 進行查找,明顯快於雙層 for 循環,for 循環消耗的時間是 key 值查找的 100多倍!!!

五、原理解析

1. 雙層 for 循環就不需要多解釋,純粹的 10000 x 100 00000 = 100 億 的遍歷次數

2. HashMap 快的原因在於將 id 值作為 map 的key存儲在map中,而 map 底層是 數組在存儲數據,此處不對鏈表和樹結構進行說明。通過計算 id 的 hashcode 值,再與 map 的容量 size 求余數,直接獲取到該條數據在 hashMap 中的下標,而不是逐一的去查找數據。故 使用 hashmap 只循環了一次 + 少量運算,速度明顯有所突破。

3. 根據需要控制內存消耗大小,你可以自定義將 數據多的放在 map 或者將數據少的放在 map 中,也就是在控制外層循環的次數,外層大,則占用內存就小,時間上可能會有所增加。

【策略與優化 - 001】- 在特定場景下,如何對雙層循環進行降級,加速數據匹配?