HashMap 常見應用:實現 SQL JOIN

在我的上一篇文章中,講到了我自己初步認識 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 數據下,執行時間如下:

clipboard.png

差距好像挺大。想了解爲什麼 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());
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章