在我的上一篇文章中,講到了我自己初步認識 HashMap 的一個經驗分享:HashMap 淺析 —— LeetCode Two Sum 刷題總結。作爲一個 CRUD 工程師,平時很少接觸到基礎組件的涉及,那麼是不是很難有機會用到 HashMap 呢?
今天,就舉一個常見的查詢例子,來看看我們如何使用 HashMap 來提高代碼的效率。
已知一個 Student 類:
public class Student {
private Long id;
private String name;
public Student(Long id, String name) {
this.id = id;
this.name = name;
}
// ---Getters And Setters---
}
和一個 Score 類:
public class Score {
private Long studentId;
private String mathScore;
private String englishScore;
public Score(Long studentId, String mathScore, String englishScore) {
this.studentId = studentId;
this.mathScore = mathScore;
this.englishScore = englishScore;
}
// ---Getters And Setters---
}
我們需要把 Student 和 Score 合併到一起,即類 Report:
public class Report {
private Long studentId;
private String studentName;
private String mathScore;
private String englishScore;
public Report(Long studentId, String studentName, String mathScore, String englishScore) {
this.studentId = studentId;
this.studentName = studentName;
this.mathScore = mathScore;
this.englishScore = englishScore;
}
}
看類的屬性我們就明白了,這裏其實相當於在 Student 和 Score 之間做一個 Join,得到 Report。這是我們在編程中常見的場景(例如查詢了訂單中心,用戶中心,支付中心,合併各個中心返回的結果形成一個表單,因爲各個中心是獨立的微服務,無法使用 SQL JOIN)。
現有兩個 List:List<Student>
和 List<Score>
:
List<Student> students = Arrays.asList(
new Student(1L, "Angle"),
new Student(2L, "Baby")
);
List<Score> scores = Arrays.asList(
new Score(1L, "90", "87"),
new Score(2L, "92", "78")
);
在學會使用 HashMap 之前,我可能會做一次雙重循環:
List<Report> reports = new ArrayList<>();
for (Student student : students) {
for (Score score : scores) {
if (!student.getId().equals(score.getStudentId())) {
continue;
}
reports.add(
new Report(student.getId(), student.getName(), score.getMathScore(), score.getEnglishScore())
);
break;
}
}
時間複雜度最差的情況下是O(n * m)
。
但是使用 HashMap 來改善程序,就能得到不錯的效果:
Map<Long, Student> map = new HashMap<>();
for (Student student : students) {
map.put(student.getId(), student);
}
List<Report> reports = new ArrayList<>();
for (Score score : scores) {
Student student = map.get(score.getStudentId());
if(student == null){ // 避免 NPE
continue;
}
reports.add(
new Report(student.getId(), student.getName(), score.getMathScore(), score.getEnglishScore())
);
}
雙重循環,變成了兩次循環,時間複雜度是O(n + m)
。
顯然要比前面的方法效果要好一些。筆者寫了測試代碼分別測試兩個方法的效率,在 10w 數據下,執行時間如下:
差距好像挺大。想了解爲什麼 HashMap 能夠得到如此好的效果,可以看我的這篇文章:HashMap 淺析 —— LeetCode Two Sum 刷題總結。如果讀者有更好的解法歡迎留言交流,筆者水平有限,在算法上研究不多。
10w 數據的測試源碼見下方,各位讀者可以自行試驗下效果:
package com.xiangyu.demo.hashmap;
import com.xiangyu.java.hashmap.Report;
import com.xiangyu.java.hashmap.Score;
import com.xiangyu.java.hashmap.Student;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class HashMapTest {
private List<Student> students = new ArrayList<>();
private List<Score> scores = new ArrayList<>();
@Before
public void before() {
// 每個list 裏放 10w 數據
for (long i = 0; i < 100000; i++) {
students.add(new Student(i, "test"));
scores.add(new Score(i, "95", "95"));
}
}
@Test
public void TestHashMap() {
Map<Long, Student> map = new HashMap<>();
for (Student student : students) {
map.put(student.getId(), student);
}
List<Report> reports = new ArrayList<>();
for (Score score : scores) {
Student student = map.get(score.getStudentId());
if (student == null) {
continue;
}
reports.add(
new Report(student.getId(), student.getName(), score.getMathScore(), score.getEnglishScore())
);
}
System.out.println(reports.size());
}
@Test
public void testFor2() {
List<Report> reports = new ArrayList<>();
for (Student student : students) {
for (Score score : scores) {
if (!student.getId().equals(score.getStudentId())) {
continue;
}
reports.add(
new Report(student.getId(), student.getName(), score.getMathScore(), score.getEnglishScore())
);
break;
}
}
System.out.println(reports.size());
}
}