Map集合(List<Map>)或者實體類集合(List<Entity>)該如何排序?
技術標籤:JavaSEjava快速排序arraylistjavase
文章目錄
前言
“排序”這兩個字想必大家如果學過演算法的話恐怕都不會陌生。但是我們在演算法的學習中往往都是用一個整數陣列來進行排序,而在實際工作中,我們往往碰到的都是實體類集合或者Map集合的形式。那麼,在這種情況下,我們該如何進行排序呢?
前期準備
實體類
我們先來準備一個實體類,待會用於生成實體類集合再排序:
package listsort;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* @ClassName Entity
* @Description 實體類 - 人類
* @Author 古闕月
* @Date 2020/12/27
* @Version 1.0
*/
//lombok外掛的註解
@Data // 若未使用lombok外掛,請自行生成getter、setter以及toString方法
@AllArgsConstructor // 若未使用lombok外掛,請自行生成有參構造方法
@NoArgsConstructor // 若未使用lombok外掛,請自行生成無參構造方法
@Accessors(chain = true) // 開啟鏈式程式設計
public class Person {
/**
* 姓名
*/
private String name;
/**
* 年齡
*/
private int age;
}
生成集合工具類
再來一個可以批量生成集合的工具類:
package listsort;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName GainList
* @Description 獲取集合的工具類
* @Author 古闕月
* @Date 2020/12/27 23:43
* @Version 1.0
*/
public class GainList {
/**
* 隨機生成包含num個map的List集合
* @param num
* @return
*/
public static List<Map> getMapList(int num) {
List<Map> mapList = new ArrayList<>();
for (int i = 0; i < num; i++) {
// 隨機生成 0-100 直接的整數
int j = (int) (Math.random() * 101);
Map map = new HashMap();
map.put("name", "李華"+j);
map.put("age", j);
mapList.add(map);
}
return mapList;
}
/**
* 隨機生成包含num個人類的List集合
* @param num
* @return
*/
public static List<Person> getPersonList(int num) {
List<Person> personList = new ArrayList<>();
for (int i = 0; i < num; i++) {
// 隨機生成 0-100 直接的整數
int j = (int) (Math.random() * 101);
Person person = new Person();
person.setName("李華"+j);
person.setAge(j);
personList.add(person);
}
return personList;
}
}
方式一
實體類集合排序
如果讓你們將一個包含實體類人類的集合按照年齡升序排序,你們會怎麼做?
可能很多小夥伴會像下面的程式碼一樣先得到一個年齡升序的陣列(反正我之前就是這麼做的,結果挨批了),然後再根據這個年齡陣列將集合中的實體類一個個抽出來:
package listsort;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @ClassName ListEntitySort
* @Description List<entity> 排序 - 老方法
* @Author 古闕月
* @Date 2020/12/25
* @Version 1.0
*/
public class ListEntitySort {
public static void main(String[] args) {
// 用於得到排序後返回的list
List<Person> sortList = new ArrayList<>();
/*
* 隨機生成包含3個人類的List集合
*/
List<Person> personList = GainList.getPersonList(3);
System.out.println("排序前:" + personList);
// 用於獲取年齡陣列
int[] ageArr = new int[personList.size()];
// 遍歷集合,獲取包含年齡的陣列
for (int i = 0; i < personList.size(); i++) {
ageArr[i] = personList.get(i).getAge();
}
// 將包含年齡的陣列排序 - 升序
Arrays.sort(ageArr);
for (int i = 0; i < ageArr.length; i++) {
for (int j = 0; j < personList.size(); j++) {
if (ageArr[i] == personList.get(j).getAge()) {
// 新增
sortList.add(personList.get(j));
// 移除,減少下次遍歷次數
personList.remove(j);
// 防止報錯
j--;
}
}
}
System.out.println("排序後:" + sortList);
}
}
執行得:
Map集合排序
Map集合排序的方式一其實跟實體類集合是一樣的,這裡我們來個降序排序:
package listsort;
import java.util.*;
/**
* @ClassName ListMapSort
* @Description List<map> 排序 - 老方法
* @Author 古闕月
* @Date 2020/12/25 16:40
* @Version 1.0
*/
public class ListMapSort {
public static void main(String[] args) {
// 用於得到排序後返回的list
List<Map> sortList = new ArrayList<>();
/*
* 隨機生成包含3個map的List集合
*/
List<Map> mapList = GainList.getMapList(3);
System.out.println("排序前:" + mapList);
// 用於獲取年齡陣列
Integer[] ageArr = new Integer[mapList.size()];
// 遍歷集合,獲取 包含年齡的陣列
for (int i = 0; i < mapList.size(); i++) {
ageArr[i] = (int) mapList.get(i).get("age");
}
// 將包含年齡的陣列排序 - 降序
Arrays.sort(ageArr, Collections.reverseOrder());
for (int i = 0; i < ageArr.length; i++) {
for (int j = 0; j < mapList.size(); j++) {
if (ageArr[i] == (int)mapList.get(j).get("age")) {
// 新增
sortList.add(mapList.get(j));
// 移除,減少下次遍歷次數
mapList.remove(j);
// 防止報錯
j--;
}
}
}
System.out.println("排序後:" + sortList);
}
}
執行得:
方式二
之前方式一的方式雖然可以實現效果,但是我們可以很容易發現兩大缺點:
- 程式碼量太多導致可讀性較差,不易於維護。
- 空間複雜度較高,new了一個新的集合。
那麼有沒有什麼方法可以改進呢? 當然有了,不然我寫這篇部落格幹嘛?
Java的api其實已經給我們提供方法了,如以實體類集合排序為例:
package listsort;
import java.util.*;
/**
* @ClassName ListEntitySort2
* @Description List<entity> 排序
* @Author 古闕月
* @Date 2020/12/25
* @Version 1.0
*/
public class ListEntitySort2 {
public static void main(String[] args) {
/*
* 隨機生成包含3個人類的List集合
*/
List<Person> personList = GainList.getPersonList(3);
System.out.println("排序前:" + personList);
/**
* 將包含3個person實體的List集合按照年齡由低到高排序
*/
Collections.sort(personList, new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
int age1 = p1.getAge();
int age2 = p2.getAge();
if (age1 > age2) return 1;
if (age1 < age2) return -1;
return 0;
}
});
System.out.println("排序後:" + personList);
}
}
是不是感覺程式碼瞬間簡潔了許多?執行得:
排序完成,very good!
比較
通過以上對比,我們可以很明顯的發現方式二的兩大優點:
- 程式碼可讀性較好,易於維護。
- 空間複雜度較低。
那麼,既然是程式,必定逃不過一個問題,那就是速率問題,或者說是時間複雜度。
讓我們來測試一下吧!!!
我們以Map集合排序為例,如用方式一對1000個Map進行排序:
package listsort;
import java.util.*;
/**
* @ClassName ListMapSort
* @Description List<map> 排序 - 老方法
* @Author 古闕月
* @Date 2020/12/25 16:40
* @Version 1.0
*/
public class ListMapSort {
public static void main(String[] args) {
// 獲取當前系統的毫秒數
long begin = System.currentTimeMillis();
// 用於得到排序後返回的list
List<Map> sortList = new ArrayList<>();
/*
* 隨機生成包含num個map的List集合
*/
int num = 1000;
List<Map> mapList = GainList.getMapList(num);
System.out.println("排序前:" + mapList);
// 用於獲取年齡陣列
int[] ageArr = new int[mapList.size()];
// 遍歷集合,獲取 包含年齡的陣列
for (int i = 0; i < mapList.size(); i++) {
ageArr[i] = (int) mapList.get(i).get("age");
}
// 將包含年齡的陣列排序 - 升序
Arrays.sort(ageArr);
for (int i = 0; i < ageArr.length; i++) {
for (int j = 0; j < mapList.size(); j++) {
if (ageArr[i] == (int)mapList.get(j).get("age")) {
// 新增
sortList.add(mapList.get(j));
// 移除,減少下次遍歷次數
mapList.remove(j);
j--; // 防止報錯
}
}
}
System.out.println("排序後:" + sortList);
// 獲取當前系統的毫秒數
long end = System.currentTimeMillis();
System.out.println("方法一排序含" + num + "個map的List集合,耗時:" + (double)(end - begin) / 1000 + "s");
}
}
執行得:
我們可以發現一號選手用時0.048秒,下面有請二號選手:
package listsort;
import java.util.*;
/**
* @ClassName ListMapSort2
* @Description List<map> 排序
* @Author 古闕月
* @Date 2020/12/25
* @Version 1.0
*/
public class ListMapSort2 {
public static void main(String[] args) {
long begin = System.currentTimeMillis();
/*
* 隨機生成包含num個map的List集合
*/
int num = 1000;
List<Map> mapList = GainList.getMapList(num);
System.out.println("排序前:" + mapList);
/**
* 將包含3個map的List集合按照年齡由低到高排序
*/
Collections.sort(mapList, new Comparator<Map>() {
@Override
public int compare(Map o1, Map o2) {
int age1 = (int) o1.get("age");
int age2 = (int) o2.get("age");
if (age1 > age2) return 1;
if (age1 < age2) return -1;
return 0;
}
});
System.out.println("排序後:" + mapList);
long end = System.currentTimeMillis();
System.out.println("方法一排序含" + num + "個map的List集合,耗時:" + (double)(end - begin) / 1000 + "s");
}
}
執行得:
這個時候,我們驚喜的發現,二號選手用時更短,僅為0.015秒,不信你多測幾次便知。
哪怕,我們對方式一進行優化,如將 包含年齡的陣列排序 的排序方法這一行程式碼
// 將包含年齡的陣列排序 - 升序
Arrays.sort(ageArr);
改為快速排序:
// 快速排序
QuickSort.quickSort(ageArr, 0, ageArr.length - 1);
雖然速率有所波動,但有時耗時會很少,有時會更多,效果仍是差強人意。所以這裡我們可以發現方式二的第三大優點:
- 時間複雜度低,也就是速率比較快。
至於快速排序速率為什麼會波動以及快速排序的程式碼,大家可以參考我的下面這篇部落格中的第四章節<快速排序>,然後自行探究驗證:
肝了幾萬字,送給看了《演算法圖解》卻是主攻Java的你和我(上篇)