關於Java使用groupingBy分組數據亂序問題

這是對最近做的一個項目,其中一個知識點的總結。

真實的業務場景就不說了,我來模擬下業務場景,足夠說明問題就行了。

假設我有個對象,存儲人員的基本信息,如下:

@AllArgsConstructor
@Data
@ToString
public class PersonInfo {
    private String name;
    private int age;
    private int sex; //0表示男性,1表示女性
}

添加一些測試數據,

        List<PersonInfo> personInfoList = new ArrayList<>();

        personInfoList.add(new PersonInfo("tom", 32, 1));
        personInfoList.add(new PersonInfo("jerry", 30, 1));
        personInfoList.add(new PersonInfo("alice", 15, 0));
        personInfoList.add(new PersonInfo("jack", 23, 1));
        personInfoList.add(new PersonInfo("aya", 32, 0));

打印下看看,

        personInfoList.forEach(e -> System.out.println(e));

PersonInfo(name=tom, age=32, sex=1)
PersonInfo(name=jerry, age=30, sex=1)
PersonInfo(name=alice, age=15, sex=0)
PersonInfo(name=jack, age=23, sex=1)
PersonInfo(name=aya, age=32, sex=0)

我們可以看到,打印的順序和我們添加的順序是一樣的。

現在我們有個需求,按照性別進行分組。

這個也不難,在 java8 環境下我們可以使用stream流的groupingBy很容易的實現,於是就有了下面的代碼,

        Map<Integer, List<PersonInfo>> map = personInfoList.stream().collect(Collectors.groupingBy(PersonInfo::getSex));

groupingBy實現類似SQL語句的“Group By”字句功能,實現根據一些屬性進行分組並把結果存在Map實例。

打印結果看看是怎樣的,

        map.forEach((key, value) -> System.out.println(key + ": " + value));

0: [PersonInfo(name=alice, age=15, sex=0), PersonInfo(name=aya, age=32, sex=0)]
1: [PersonInfo(name=tom, age=32, sex=1), PersonInfo(name=jerry, age=30, sex=1), PersonInfo(name=jack, age=23, sex=1)]

結果確實是按照要求分組了,但是PersonInfo對象的順序和我們之前添加的順序不一樣了,在有些業務場景下,這種結果往往是不允許的。(比如分頁查詢等)

爲什麼順序會亂的?

我們先來看看這個map是哪個類型的,

        System.out.println(map.getClass().getSimpleName());

打印出來的結果是 HashMap

這個就解釋了爲啥順序被打亂了, HashMap在存儲是數據時,是先用key做hash計算,然後根據hash的結果決定這條數據的位置,因爲hash本身是無序的,導致了讀出的順序是亂的。

知道了原因後,那就很容易想到解決方案了, groupingBy有一個重載的方法,允許我們指定map進行操作,

Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream)

注意看第二個參數, Supplier是java8提供的一中函數接口類型,用於提供一個對象, 根據尖括號裏的定義,這裏需要提供一個Map類型的對象。

另外我們知道HashMapLinkedHashMap的區別是後者通過雙向鏈表保證數據插入的順序和訪問的順序一致。 於是就有了下面的代碼,

        Map<Integer, List<PersonInfo>> map = personInfoList.stream().collect(Collectors.groupingBy(PersonInfo::getSex, LinkedHashMap::new, Collectors.toList()));

打印的結果是,

1: [PersonInfo(name=tom, age=32, sex=1), PersonInfo(name=jerry, age=30, sex=1), PersonInfo(name=jack, age=23, sex=1)]
0: [PersonInfo(name=alice, age=15, sex=0), PersonInfo(name=aya, age=32, sex=0)]

可以看到打印的順序和原來的list順序是一樣的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章