最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java使用 Stream 流和 Lambda 組裝復(fù)雜父子樹形結(jié)構(gòu)

 更新時間:2024年07月02日 09:12:52   作者:CodeBlogMan  
在最近的開發(fā)中,遇到了兩個類似的需求:都是基于 Stream 的父子樹形結(jié)構(gòu)操作,返回 List 集合對象給前端,下面給大家分享Java使用 Stream 流和 Lambda 組裝復(fù)雜父子樹形結(jié)構(gòu)的相關(guān)操作,感興趣的朋友跟隨小編一起看看吧

前言

在最近的開發(fā)中,遇到了兩個類似的需求:都是基于 Stream 的父子樹形結(jié)構(gòu)操作,返回 List 集合對象給前端。于是在經(jīng)過需求分析和探索實(shí)踐后有了新的認(rèn)識,現(xiàn)在拿出來和大家作分享交流。

一般來說完成這樣的需求大多數(shù)人會想到遞歸,但遞歸的方式弊端過于明顯:方法多次自調(diào)用效率很低、數(shù)據(jù)量大容易導(dǎo)致堆棧溢出、隨著樹深度的增加其時間復(fù)雜度會呈指數(shù)級增加等。

核心思路如下:

  • 數(shù)據(jù)庫全量查詢(幾萬條),內(nèi)存中使用 stream 流操作、Lambda 表達(dá)式、Java 地址引用

  • 使用緩存注解(底層Redis分布式緩存實(shí)現(xiàn)),過期后自動更新緩存,再次調(diào)用接口則先命中緩存,沒有的話再查數(shù)據(jù)庫

  • 使用 MQ 來做異步通知更新,即當(dāng)數(shù)據(jù)有更改時,可以異步將數(shù)據(jù)先更新,再寫入緩存,使業(yè)務(wù)更合理,考慮更全面

一、以企業(yè)部門結(jié)構(gòu)為例

這里的實(shí)體是放在 MySQL 里的,使用簡單的封裝好的查詢語句,這個很簡單。

1.1實(shí)體

部門表:一個公司里都會有許多的部門,一個部門里還會有部門。從最頂層到你所在的的部門,可能會有多達(dá)六、七層。以下只展示核心字段:

@Data
public class Department {
    /**
     * 主鍵Id
     */
    private Integer id;
    /**
     * 該部門的父部門Id
     */
    private Integer parentDeptId;
    /**
     * 真正部門Id
     */
    private Integer deptId;
    /**
     * 部門的名稱
     */
    private String name;
    /**
     * 部門在結(jié)構(gòu)中所處的層級
     */
    private Integer level;
    /**
     * 狀態(tài)是否啟用
     */
    private Integer status;
}

1.2返回VO

這個返回的VO是給前端的,里面的子節(jié)點(diǎn)集合屬性 childrenList,是一個關(guān)鍵字段,所有該方式返回樹結(jié)構(gòu)的 VO 都需要有該字段來”封裝自己“。

@Data
public class DepartmentVO implements Serializable {
    /**
     * 子節(jié)點(diǎn)集合,封裝自己
     */
    private List<DepartmentVO> childrenList;
    /**
     * 部門Id
     */
    protected Integer deptId;
    /**
     * 父部門Id
     */
    protected Integer parentDeptId;
    /**
     * 部門名稱
     */
    protected String name;
}

1.3具體實(shí)現(xiàn)

下面直接上 demo 代碼,注釋已經(jīng)說的比較清楚了:

    @Override
    public List<DepartmentVO> departmentStructure(String id){
            //step1:這里 map 只是簡單轉(zhuǎn)換了返回的對象屬性(返回需要的類型),本質(zhì)還是所有部門數(shù)據(jù)
            List<DepartmentVO> departmentVOList = this.getDepartmentListById(id).stream()
                    .map(e -> e.copyProperties(DepartmentVO.class))
                    .collect(Collectors.toList());
            //step2:利用父節(jié)點(diǎn)分組,所有部門的父 Id 進(jìn)行分組,把所有的子節(jié)點(diǎn) List 集合都找出來并一層層分好組
            Map<Integer, List<DepartmentVO>> departmentListMap = departmentVOList.stream()
                    .collect(Collectors.groupingBy(DepartmentVO::getParentDeptId));
            //step3:關(guān)鍵一步,關(guān)聯(lián)上子部門,將子部門的 List 集合經(jīng)過遍歷一層層地放置好,最終會得到完整的部門父子關(guān)系 List 集合
            departmentVOList.forEach(e -> e.setChildrenList(departmentListMap.get(e.getDeptId())));
            //step4:過濾出頂級部門,即所有的子部門數(shù)據(jù)都?xì)w屬于一個頂級父部門 Id
            List<DepartmentVO> resultList = departmentVOList.stream()
                    .filter(e -> Constants.TOP_DEPARTMENT_NUM.equals(e.getParentDeptId()))
                    .collect(Collectors.toList());
        return Optional.of(resultList).orElse(null);
    }

1.4效果展示

我這里測試的例子是只有三層,數(shù)據(jù)也沒有完全展開,當(dāng)然五六層也是沒問題的。

只要總的部門數(shù)據(jù)量在一兩萬條以內(nèi)(啥情況部門數(shù)量會有幾萬個?部門表一般是獨(dú)立于其它表的)速度都是比較快的,服務(wù)器性能(主要內(nèi)存給力)好的話,基本整個請求/響應(yīng)(拋開網(wǎng)絡(luò)I/O消耗)可以在一秒內(nèi)完成。

部門結(jié)構(gòu)效果圖

二、以中國行政區(qū)域結(jié)構(gòu)為例

實(shí)體只需要使用一次查全量的語句,沒有其它別的操作,很大程度上是因?yàn)槭∈锌h的結(jié)構(gòu)是比較固定的。

2.1實(shí)體

全國行政區(qū)表:全國的行政區(qū)包括省/直轄市/自治區(qū)、地級市、區(qū)/縣級市/縣這三級,再往下的街道/鎮(zhèn)、以及下面的村/小組就不包含了。同樣也是只留關(guān)鍵屬性:

@Data
public class Area {
    /**
     * 地區(qū)id
     */
    public Long id;
    /**
     * 父Id
     */
    public Long parentId;
    /**
     * 地區(qū)名稱
     */
    public String name;
    /**
     * 所屬省Id
     */
    public Long provinceId;
    /**
     * 所屬地級市Id
     */
    public Long cityId;
    /**
     * 所處層級
     */
    public Integer level;
}

2.2返回VO

同樣,這個里面的子節(jié)點(diǎn)集合屬性 childrenAreaVOList,是一個關(guān)鍵字段,所有該方式返回樹結(jié)構(gòu)的 VO 都需要有該字段來”封裝自己“。

@Data
public class AreaVO {
    /**
     * 子節(jié)點(diǎn) list 集合
     */
    private List<AreaVO> childrenAreaVOList;
    /**
     * 區(qū)域id
     */
    public Long id;
    /**
     * 地區(qū)名稱
     */
    public String name;
    /**
     * 所處層級
     */
    public Integer level;
    /**
     * 父Id
     */
    public Long parentId;
    /**
     * 所屬省Id
     */
    public Long provinceId;
    /**
     * 所屬地級市Id
     */
    public Long cityId;
}

2.3具體實(shí)現(xiàn)

下面同樣直接上 demo 代碼,注釋比較詳細(xì):

    @Override
    public List<AreaVO> getAreaStructure() {
        //第一步,從數(shù)據(jù)庫中查出所有數(shù)據(jù),按照排序條件進(jìn)行排序,本質(zhì)上還是這個所有數(shù)據(jù)的 List 集合
        List<AreaVO> areaVOList = this.findAll(Sort.by("id").descending()).stream()
                //注:這里使用 map 映射了需要返回的 VO,即相同的屬性字段就會轉(zhuǎn)換
                .map(e -> e.copyProperties(AreaVO.class)).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(areaVOList)){
            //第二步,根據(jù)父Id 字段進(jìn)行分組,即所有數(shù)據(jù)都會按照第一層至最后一層都按照父子關(guān)系進(jìn)行分組;注意,是對所有數(shù)據(jù)分組
            Map<Long, List<AreaVO>> areaVoListMap = areaVOList.parallelStream().collect(Collectors.groupingBy(AreaVO::getParentId));
            //第三步,也是最關(guān)鍵的一步,將所有子數(shù)據(jù) List 集合經(jīng)過遍歷后都一層層地放置好,最終會得到一個包含父子關(guān)系的完整List
            areaVOList.forEach(e -> e.setChildrenAreaVOList(areaVoListMap.get(e.getId())));
            //第四步,過濾出符合頂層父Id的所有數(shù)據(jù),即所有數(shù)據(jù)都?xì)w屬于一個頂層父Id
            List<AreaVO> resultList = areaVOList.stream()
                    .filter(e -> Constants.COUNTRY_CHINA_TOP_NUM.equals(e.getParentId()))
                    .collect(Collectors.toList());
            return Optional.of(resultList).orElse(null);
        }
        return new ArrayList<>();
    }

2.4效果展示

我這里測試環(huán)境的例子是只有省/直轄市/自治區(qū)、地級市、區(qū)/縣級市/縣這三級,數(shù)據(jù)也沒有完全展開,當(dāng)然到下面的鎮(zhèn)/街道,乃至村/小組也是沒問題的。

這里總的測試數(shù)據(jù)量是幾千條,如果加上鎮(zhèn)/街道應(yīng)該得有幾萬條,速度也還是是比較快的,服務(wù)器性能(主要內(nèi)存給力)好的話,基本整個請求/響應(yīng)(拋開網(wǎng)絡(luò)I/O消耗)可以在一秒內(nèi)完成。

中國行政區(qū)域信息層次結(jié)構(gòu)效果

時間消耗,這里響應(yīng)只有兩百多毫秒,如下圖的接口的性能展示:

接口性能展示

三、文章小結(jié)

使用 Stream 流組裝復(fù)雜父子樹形結(jié)構(gòu)(List 集合形式)的分享到這里就結(jié)束了,編碼沒有捷徑,都是項(xiàng)目實(shí)踐里出真知,一點(diǎn)點(diǎn)摸索攢經(jīng)驗(yàn)。

相關(guān)文章

  • MAC算法之消息摘要算法HmacMD5的實(shí)現(xiàn)

    MAC算法之消息摘要算法HmacMD5的實(shí)現(xiàn)

    這篇文章主要介紹了MAC算法之消息摘要算法HmacMD5的實(shí)現(xiàn)的相關(guān)資料,這里提供實(shí)例,幫助大家學(xué)習(xí)理解這部分知識,需要的朋友可以參考下
    2017-08-08
  • struts2框架入門

    struts2框架入門

    本文主要介紹了struts2框架的基礎(chǔ)入門知識。具有很好的參考價值。下面跟著小編一起來看下吧
    2017-03-03
  • Sentinel?Gateway自定義限流返回結(jié)果方式

    Sentinel?Gateway自定義限流返回結(jié)果方式

    這篇文章主要介紹了Sentinel?Gateway自定義限流返回結(jié)果方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Java class文件格式之方法_動力節(jié)點(diǎn)Java學(xué)院整理

    Java class文件格式之方法_動力節(jié)點(diǎn)Java學(xué)院整理

    這篇文章主要為大家詳細(xì)介紹了Java class文件格式之方法的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • SpringMVC 整合SSM框架詳解

    SpringMVC 整合SSM框架詳解

    這篇文章主要介紹了SpringMVC 整合SSM框架詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • SpringBoot整合微信小程序支付V3(支付退款)

    SpringBoot整合微信小程序支付V3(支付退款)

    小程序支付在很多項(xiàng)目都會使用,本文主要介紹了SpringBoot整合微信小程序支付V3,具有一定的參考價值,感興趣的可以了解一下
    2023-09-09
  • idea粘貼空格時顯示NBSP的問題及解決方案

    idea粘貼空格時顯示NBSP的問題及解決方案

    在IDEA中粘貼代碼時出現(xiàn)大量空格占位符NBSP,可以通過取消勾選AdvancedSettings中的相應(yīng)選項(xiàng)來解決
    2025-12-12
  • Java 消息的可靠性投遞實(shí)踐建議

    Java 消息的可靠性投遞實(shí)踐建議

    文章詳細(xì)介紹了消息可靠性投遞(ReliableDelivery)的概念、面臨的挑戰(zhàn)及關(guān)鍵實(shí)現(xiàn)機(jī)制,生產(chǎn)端通過事務(wù)機(jī)制、確認(rèn)機(jī)制、本地消息表和消息持久化等技術(shù)保證消息不丟失、不重復(fù)、按順序傳遞,感興趣的朋友跟隨小編一起看看吧
    2025-12-12
  • Java編程時間日期API實(shí)例解析

    Java編程時間日期API實(shí)例解析

    本文主要介紹了Java編程時間日期API實(shí)例解析的相關(guān)內(nèi)容,分享了一則實(shí)例,具有一定借鑒價值,需要的朋友可以參考下。
    2018-01-01
  • Mybatis-plus數(shù)據(jù)權(quán)限D(zhuǎn)ataPermissionInterceptor實(shí)現(xiàn)

    Mybatis-plus數(shù)據(jù)權(quán)限D(zhuǎn)ataPermissionInterceptor實(shí)現(xiàn)

    本文主要介紹了Mybatis-plus數(shù)據(jù)權(quán)限D(zhuǎn)ataPermissionInterceptor實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07

最新評論

常山县| 昌江| 新营市| 乐亭县| 富裕县| 繁昌县| 中江县| 阳山县| 信宜市| 大田县| 乐清市| 怀来县| 扶风县| 司法| 五台县| 怀仁县| 洛浦县| 塔城市| 钟祥市| 宁德市| 馆陶县| 田东县| 昭苏县| 河北省| 临江市| 云和县| 社旗县| 鞍山市| 抚州市| 新疆| 富蕴县| 灵山县| 通海县| 霍城县| 门头沟区| 年辖:市辖区| 襄汾县| 绵阳市| 包头市| 成安县| 垫江县|