【策略與優化 - 001】- 在特定場景下,如何對雙層循環進行降級,加速數據匹配?
一、場景介紹
假設某次搜索結果中有 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 = newArrayList<>(); // 收藏的數據條數 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】- 在特定場景下,如何對雙層循環進行降級,加速數據匹配?