在工作中,經常性的會出現在兩張表中查找相同ID的數據,許多開發者會使用兩層for循環嵌套,雖然實現功能沒有問題,但是效率極低,一下是一個簡單的優化過程,代碼耗時湊從26856ms優化到了748ms。
功能場景
有兩份List類型的數據,分別是UestList(用戶表)和AccountList(賬戶表),要根據用戶的id從AccountList表中查找對應的賬戶信息,并進行后續的處理,示例如下:
存數據(偽代碼):5w條user數據,3w條Account數據?
@Dataclass User{private Long userId;private String name;}@Dataclass Account{private Long userId;private String content;}public class NestedLoopOptimization{public static List<User> getUserList(){List<User> users =new ArrayList<>();for(inti =1; i <=50000; i++) {User user =newUser();user.setName(UUID.randomUUID().toString());user.setUserId((long) i);users.add(user);}return users;}public static List<UserMemo> getAccountTestList(){List<Account> accountList =newArrayList<>();for(inti =30000; i >=1; i--) {Account account =new Account();account.setContent(UUID.randomUUID().toString());account.setUserId((long) i);accountList.add(account);}return accountList;}// ... 后續代碼
最直接的實現方式(未優化的代碼):
public static void nestedLoop(List<User> usersList, List<UserMemo> accountList) {for (User user : usersList) {Long userId = user.getUserId();for (Account account : accountList) {if (userId.equals(account.getUserId())) {String content = account.getContent();// System.out.println("模擬數據content 業務處理......" + content); // 避免打印影響測試結果}}}
}
?耗時:約數萬毫秒,效率很低,數據量小的話無關緊要,如果隨著系統的迭代數據量驟增的時候,就會極其耗時。
第一步優化:添加break?
每個userId在AccountList中只有一條對應的數據,所以找到匹配項之后就可以跳出內循環:
public static void nestedLoop(List<User> usersList, List<UserMemo> accountList) {for (User user : usersList) {Long userId = user.getUserId();for (Account account : accountList) {if (userId.equals(account.getUserId())) {String content = account.getContent();// System.out.println("模擬數據content 業務處理......" + content); // 避免打印影響測試結果break;}}}
}
第一步優化結束之后任需要很多耗時,但是比起嵌套循環好很多。
第二步優化:使用Map優化?
public static void mapOptimizedLoop(List<User> userTestList, List<UserMemo> accountList) {Map<Long, String> contentMap = accountList.stream().collect(Collectors.toMap(UserMemo::getUserId, UserMemo::getContent));for (User user : userTestList) {Long userId = user.getUserId();String content = contentMap.get(userId);if (StringUtils.hasLength(content)) {// System.out.println("模擬數據content 業務處理......" + content); // 避免打印影響測試結果}}}
做以上優化之后,耗時顯著減少,通常在數百毫秒級別。
原理:
????????兩層 for 循環嵌套的時間復雜度為 O(n*m),其中 n 和 m 分別為兩個列表的長度。使用 Map 后,get 操作的時間復雜度接近 O(1),整體時間復雜度降為 O(n+m),避免了內循環的重復遍歷。HashMap 的 get 方法內部使用了 getNode 方法來查找鍵值對。getNode 方法利用哈希表結構,快速定位到目標鍵值對。雖然在極端情況下(所有鍵的哈希值都相同),getNode 的時間復雜度會退化為 O(n),但在實際應用中,哈希沖突的概率很低,HashMap 的 get 操作效率通常很高。因此無需過于擔心 O(n) 的最壞情況。?
? ? ? ? 通過以上優化之后,可以顯著的提高代碼的執行效率,已經其健壯性,尤其是在處理大數據量的時候,使用Map優化,可以帶來巨大的性能提升,避免了不必要的計算,從而實現了代碼的性能。