预备资料

实体类

  1. import lombok.Data;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. @Data
  5. public class Area {
  6. private Long id;
  7. private String name;
  8. private Long parentId;
  9. private List<Area> children = new ArrayList<>();
  10. }

区域数据

3660条
area.json
783562条
data.json

  1. public List<Area> getAreaList() {
  2. String areaStr = FileUtil.readString("area.json", StandardCharsets.UTF_8);
  3. // String areaStr = FileUtil.readString("data.json", StandardCharsets.UTF_8);
  4. return JSONUtil.toList(areaStr, Area.class);
  5. }

递归方式

代码

  1. public long recursionBuildAreaData(List<Area> areaList) {
  2. System.out.println("开始时间:" + new Date());
  3. TimeInterval timer = new TimeInterval();
  4. timer.start();
  5. // 找出所有一级区域 调用递归方法查找子区域
  6. List<Area> areas = areaList.stream().filter(area -> area.getParentId() == 0)
  7. .peek(area -> area.setChildren(getChildrenArea(area, areaList)))
  8. .collect(Collectors.toList());
  9. long result = timer.intervalMs();
  10. System.out.println("递归方式,耗时:" + result + "ms");
  11. System.out.println("结束时间:" + new Date());
  12. return result;
  13. }
  14. private List<Area> getChildrenArea(Area area, List<Area> areaList) {
  15. // 找出parentId为当前区域id的所有子区域 对子区域再进行递归
  16. return areaList.stream().filter(item -> item.getParentId().equals(area.getId()))
  17. .peek(item -> item.setChildren(getChildrenArea(item, areaList))
  18. ).collect(Collectors.toList());
  19. }

耗时

3660条 200毫秒左右
image.png
783562条 2小时9分钟
1d67bccaaa5f0876c8c37e92f9791fc.png

先转map再循环方式

代码

  1. public long toMapThanFor(List<Area> areaList) {
  2. TimeInterval timer = new TimeInterval();
  3. timer.start();
  4. // 先转成Map key为id value为Area
  5. Map<Long, Area> areaMap = areaList.stream()
  6. .collect(Collectors.toMap(Area::getId, area -> area));
  7. List<Area> areas = new ArrayList<>();
  8. for (Area area : areaList) {
  9. // 不是根节点 则添加到其parentId的区域的children中
  10. if (area.getParentId() != 0) {
  11. areaMap.get(area.getParentId()).getChildren().add(area);
  12. } else {
  13. areas.add(area);
  14. }
  15. }
  16. long result = timer.intervalMs();
  17. System.out.println("先转为Map,再遍历的方式,耗时:" + result + "ms");
  18. return result;
  19. }
  20. @Test
  21. public void testToMapThanFor() {
  22. List<Area> areaList = getAreaList();
  23. toMapThanFor(areaList);
  24. }

耗时

image.png
image.png

边循环边填充map方式

代码

  1. public long optimizationToMapThanFor(List<Area> areaList) {
  2. TimeInterval timer = new TimeInterval();
  3. timer.start();
  4. Map<Long, Area> areaMap = new HashMap<>();
  5. List<Area> areas = new ArrayList<>();
  6. for (Area area : areaList) {
  7. areaMap.putIfAbsent(area.getId(), area);
  8. // 不是根节点 则添加到其parentId的区域的children中
  9. if (area.getParentId() != 0) {
  10. areaMap.putIfAbsent(area.getParentId(), area);
  11. areaMap.get(area.getParentId()).getChildren().add(area);
  12. } else {
  13. areas.add(area);
  14. }
  15. }
  16. long result = timer.intervalMs();
  17. System.out.println("边循环,边填充map的方式,耗时:" + result + "ms");
  18. return result;
  19. }
  20. @Test
  21. public void testToMapThanFor2() {
  22. List<Area> areaList = getAreaList();
  23. optimizationToMapThanFor(areaList);
  24. }

耗时

image.png
image.png

速度比对

代码

  1. @Test
  2. public void testMain() {
  3. TimeInterval timer = new TimeInterval();
  4. timer.start();
  5. List<Area> areaList = getAreaList();
  6. int l1Count = 0;
  7. int l2Count = 0;
  8. int l3Count = 0;
  9. for (int i = 0; i < 100; i++) {
  10. long l1 = toMapThanFor(areaList);
  11. long l2 = optimizationToMapThanFor(areaList);
  12. if (l1 > l2) {
  13. l2Count++;
  14. } else if (l1 < l2) {
  15. l1Count++;
  16. } else {
  17. l3Count++;
  18. }
  19. System.out.println();
  20. }
  21. System.out.println("区域数据:" + areaList.size());
  22. System.out.println("次数多,即耗时少");
  23. System.out.println("(普通)先转为Map,再遍历的方式:" + l1Count + "次");
  24. System.out.println("(优化)边循环,边填充map的方式:" + l2Count + "次");
  25. System.out.println("相同速度:" + l3Count + "次");
  26. }

耗时

image.pngimage.pngimage.png
image.pngimage.png
image.png

总结

数据量少时

速度上体现不出差距,基本都是0ms,先转map的代码比较简洁些
image.png

数据量多时

边循环边填充map的方式会稍微快一些
image.png

总述

平时使用”先转map再循环方式”即可,代码量简洁清晰点,有庞大数据时才使用”边循环边填充map方式”

完整代码

import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONUtil;
import org.junit.Test;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


public class MyTest {
    // 获取区域列表
    public List<Area> getAreaList() {
        TimeInterval timer = new TimeInterval();
        timer.start();

        String areaStr = FileUtil.readString("area.json", StandardCharsets.UTF_8);
//                String areaStr = FileUtil.readString("data.json", StandardCharsets.UTF_8);

        List<Area> areaList = JSONUtil.toList(areaStr, Area.class);
        System.out.println("读取区域数据:" + areaList.size() + "条" + ",耗时:" + timer.intervalMs() + "ms\n");
        return areaList;
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    // 递归组装数据
    public long recursionBuildAreaData(List<Area> areaList) {
        TimeInterval timer = new TimeInterval();
        timer.start();
        // 找出所有一级区域 调用递归方法查找子区域
        List<Area> areas = areaList.stream().filter(area -> area.getParentId() == 0)
                .peek(area -> area.setChildren(getChildrenArea(area, areaList)))
                .collect(Collectors.toList());
        long result = timer.intervalMs();
        System.out.println("递归方式,耗时:" + result + "ms");
        return result;
    }

    private List<Area> getChildrenArea(Area area, List<Area> areaList) {
        // 找出parentId为当前区域id的所有子区域 对子区域再进行递归
        return areaList.stream().filter(item -> item.getParentId().equals(area.getId()))
                .peek(item -> item.setChildren(getChildrenArea(item, areaList))
                ).collect(Collectors.toList());
    }


    @Test
    public void testRecursionBuildAreaData() {
        List<Area> areaList = getAreaList();
        recursionBuildAreaData(areaList);
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public long toMapThanFor(List<Area> areaList) {
        TimeInterval timer = new TimeInterval();
        timer.start();

        // 先转成Map key为id value为Area
        Map<Long, Area> areaMap = areaList.stream().collect(Collectors.toMap(Area::getId, area -> area));
        List<Area> areas = new ArrayList<>();

        for (Area area : areaList) {
            // 不是根节点 则添加到其parentId的区域的children中
            if (area.getParentId() != 0) {
                areaMap.get(area.getParentId()).getChildren().add(area);
            } else {
                areas.add(area);
            }
        }

        long result = timer.intervalMs();
        System.out.println("先转为Map,再遍历的方式,耗时:" + result + "ms");
        return result;
    }

    @Test
    public void testToMapThanFor() {
        List<Area> areaList = getAreaList();
        toMapThanFor(areaList);
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    public long optimizationToMapThanFor(List<Area> areaList) {
        TimeInterval timer = new TimeInterval();
        timer.start();

        Map<Long, Area> areaMap = new HashMap<>();
        List<Area> areas = new ArrayList<>();

        for (Area area : areaList) {
            areaMap.putIfAbsent(area.getId(), area);

            // 不是根节点 则添加到其parentId的区域的children中
            if (area.getParentId() != 0) {
                areaMap.putIfAbsent(area.getParentId(), area);

                areaMap.get(area.getParentId()).getChildren().add(area);
            } else {
                areas.add(area);
            }
        }

        long result = timer.intervalMs();
        System.out.println("边循环,边填充map的方式,耗时:" + result + "ms");
        return result;
    }

    @Test
    public void testToMapThanFor2() {
        List<Area> areaList = getAreaList();
        optimizationToMapThanFor(areaList);
    }

    @Test
    public void testMain() {
        TimeInterval timer = new TimeInterval();
        timer.start();
        List<Area> areaList = getAreaList();
        int l1Count = 0;
        int l2Count = 0;
        int l3Count = 0;
        for (int i = 0; i < 100; i++) {
            long l1 = toMapThanFor(areaList);
            long l2 = optimizationToMapThanFor(areaList);
            if (l1 > l2) {
                l2Count++;
            } else if (l1 < l2) {
                l1Count++;
            } else {
                l3Count++;
            }
            System.out.println();
        }
        System.out.println("区域数据:" + areaList.size());
        System.out.println("次数多,即耗时少");
        System.out.println("(普通)先转为Map,再遍历的方式:" + l1Count + "次");
        System.out.println("(优化)边循环,边填充map的方式:" + l2Count + "次");
        System.out.println("相同速度:" + l3Count + "次");
    }


}