树结构通用封装

背景:在写代码的过程中,经常有些数据是树形结构的数据,如:常见的组织数据,区划信息,以及经常操作的文件夹等等… 但是数据库中存储的数据都是按照行进行存储,用pid表示对应的父子关系,所以获取真正的树结构需要对数据库数据进行加工,这个过程叫树的构建。 下面用java语言提供了一种通用的树形结构的构建方式。希望对大家有帮助

工具类主要实现逻辑:

核心方法:

  • 根据传入的ids,构建树结构并输出;
  • 根据传入的父ids,向下追溯所有的子节点信息;
  • 根据传入的子节点ids,向上追溯所有的父节点信息;
@Component
public class TreeGenerateBaseUtils<R extends TreeBuildResponseResult, P extends TreeBuildRequestParam> {


    /**
     * 把原始数据组装成树形结构
     *
     * @param param
     * @param queryFunction
     * @return 时间复杂度O(N)
     */
    public List<R> buildTree(P param, QueryFunction queryFunction) {
        // 1.获取所有的原始数据
        List<R> originData = queryFunction.getOriginData();

        if (CollectionUtils.isEmpty(originData)) {
            return null;
        }
        // 获取pid到对应直属子节映射关系
        Map<Long, List<R>> pidToEntity = originData.stream().collect(Collectors.groupingBy(R::getPid));
        // 获取id与实体之间的映射
        Map<Long, R> idToEntity = originData.stream().collect(Collectors.toMap(R::getId, Function.identity()));

        // 2.获取根节点信息
        List<R> rootInfos = getRootInfos(param, idToEntity, pidToEntity);

        // 定义Set集合,防止出现环
        Set<R> infos = new HashSet<>();
        // 遍历根节点,组装每一棵树
        for (R root : rootInfos) {
            // 利用队列宽度优先遍历,依次组装每个节点及其直属子节点
            Queue<R> queue = new LinkedList<>();
            queue.add(root);
            while (!queue.isEmpty()) {
                R parentInfo = queue.poll();
                // 防止节点重复进入,避免出现环
                if (infos.contains(parentInfo)) {
                    continue;
                }
                infos.add(parentInfo);
                List<R> children = pidToEntity.get(parentInfo.getId());
                if (!CollectionUtils.isEmpty(children)) {
                    // 组装当前节点及其直属子节点
                    addLevelInfo(parentInfo, children);
                    // 加入队列,循环组装子节点的直属子节点
                    queue.addAll(children);
                }
            }
        }
        return rootInfos;
    }


    private List<R> getRootInfos(P param, Map<Long, R> idToEntity, Map<Long, List<R>> pidToEntity) {
        List<R> rootInfo = new ArrayList<>();
        List<Long> parentIds = param.getPids();
        for (Long pid : parentIds) {
            R entity = idToEntity.get(pid);
            if (entity == null) {
                continue;
            }
            rootInfo.add(entity);
        }
        // 如果传入为整体的根节点(即自定义的根节点,表中不存在对应主键)
        if (CollectionUtils.isEmpty(rootInfo)) {
            for (Long pid : parentIds) {
                List<R> root = pidToEntity.get(pid);
                if (CollectionUtils.isEmpty(root)) {
                    continue;
                }
                rootInfo.addAll(root);
            }
        }
        return rootInfo;
    }


    /**
     * 根据parentIds查询链路上所有子节点信息,组装成map返回
     *
     * @param parentIds
     * @param queryFunction 查询条件
     * @return Map<Long, List < R>>  parentId ----> allChildInfos
     */
    public Map<Long, List<R>> getAllChildInfos(List<Long> parentIds, QueryFunction queryFunction) {
        // 获取所有原始数据
        List<R> originData = queryFunction.getOriginData();

        // key:父节点id  value:父节点往下追溯,链路上所有节点信息
        Map<Long, List<R>> allChildInfosMap = new HashMap<>();
        if (CollectionUtils.isEmpty(originData)) {
            return allChildInfosMap;
        }
        // 组装id-->entityInfos映射
        Map<Long, List<R>> pidToChildInfos = originData.stream().collect(Collectors.groupingBy(R::getPid));

        // 遍历组装的树列表
        for (Long pid : parentIds) {
            List<R> allChildInfos = new ArrayList<>();
            // 获取pid下所有子节点信息,存放到allChildInfos里面
            getChildInfos(pid, pidToChildInfos, allChildInfos);
            allChildInfosMap.put(pid, allChildInfos);
        }
        return allChildInfosMap;
    }


    /**
     * 根据childIds向上查询链路上所有信息
     *
     * @param childIds
     * @param queryFunction
     * @return child --> List<R>
     */
    public Map<Long, List<R>> getUpFullLinkByChildIds(List<Long> childIds, QueryFunction queryFunction) {
        // 获取所有原始数据
        List<R> originData = queryFunction.getOriginData();

        // key:子节点id  value:子节点往上追溯,链路上所有节点信息
        Map<Long, List<R>> childIdToUpLinkInfos = new HashMap<>();
        if (CollectionUtils.isEmpty(originData)) {
            return childIdToUpLinkInfos;
        }
        // 组装id-->entity映射
        Map<Long, R> idToEntity = originData.stream().collect(Collectors.toMap(R::getId, Function.identity()));

        for (Long id : childIds) {
            List<R> result = new ArrayList<>();
            // 获取childId向上追溯的所有实体信息,放到result里面
            getUpFullLinkInfo(id, idToEntity, result);
            // 反转添加的链路信息,从顶节点到子节点
            Collections.reverse(result);
            // 组装结果
            childIdToUpLinkInfos.put(id, result);
        }
        return childIdToUpLinkInfos;
    }


    /**
     * 提供子类扩展使用,子类可以重写对应方法
     *
     * @param parentInfo
     * @param children
     */
    protected void addLevelInfo(R parentInfo, List<R> children) {
        parentInfo.setChildren((List<TreeBuildResponseResult>) children);
    }

    // 根据childId向上递归查找父节点信息
    private void getUpFullLinkInfo(Long childId, Map<Long, R> idToEntity, List<R> result) {
        R entity = idToEntity.get(childId);
        if (entity == null) {
            return;
        }
        result.add(entity);
        getUpFullLinkInfo(entity.getPid(), idToEntity, result);
    }


    private void getChildInfos(Long pid, Map<Long, List<R>> pidToChildInfos, List<R> allChildIds) {
        List<R> results = pidToChildInfos.get(pid);
        if (CollectionUtils.isEmpty(results)) {
            return;
        }
        // 获取所有直属子节点信息
        allChildIds.addAll(results);

        // 遍历所有直属子节点
        for (R child : results) {
            // 递归处理,深度优先添加链路上的所有子节点信息
            getChildInfos(child.getId(), pidToChildInfos, allChildIds);
        }
    }


}

其中入参和出参定义了些通用的基本的信息,在工具类中都采用泛型的处理,对应的实现者可以继承进行相应的拓展处理,如下:

@Data
public class TreeBuildRequestParam {


    private List<Long> pids;

}
@Data
@Builder
public class TreeBuildResponseResult {

    private Long id;

    private Long pid;


    private String name;

    private String code;


    private List<TreeBuildResponseResult> children;

}

函数式接口留给使用者传递对应的实现,获取数据库中的原始数据:

@FunctionalInterface
public interface QueryFunction<R extends TreeBuildResponseResult> {

    /**
     * 获取原始数据
     *
     * @return
     */
    List<R> getOriginData();
}

工具类的mock使用如下:

public class Test {


    /**
     * 构建树
     */
    public void testBuildTree() {
        TreeGenerateBaseUtils baseUtils = new TreeGenerateBaseUtils();
        TreeBuildRequestParam requestParam = new TreeBuildRequestParam();
        List<Long> parentIds = new ArrayList<>();
        parentIds.add(1l);
        requestParam.setPids(parentIds);
        // 组装树结构
        List<TreeBuildResponseResult> treeResult = baseUtils.buildTree(requestParam, () -> mockData());
        System.out.println(JSON.toJSONString(treeResult));
    }


    /**
     * 根据父ids向下查询所有子节点信息
     */
    public void testGetChildInfos() {
        TreeGenerateBaseUtils baseUtils = new TreeGenerateBaseUtils();
        List<Long> parentIds = new ArrayList<>();
        parentIds.add(1l);
        Map<Long, List<TreeBuildResponseResult>> allChildIds = baseUtils.getAllChildInfos(parentIds, () -> mockData());
        System.out.println(JSON.toJSONString(allChildIds));
    }


    /**
     * 根据子节点ids向上查询所有父节点信息
     */
    public void testGetUpFullLinkByChildIds() {
        TreeGenerateBaseUtils baseUtils = new TreeGenerateBaseUtils();
        List<Long> childIds = new ArrayList<>();
        childIds.add(7l);
        Map<Long, List<TreeBuildResponseResult>> resultMap = baseUtils.getUpFullLinkByChildIds(childIds, () -> mockData());
        System.out.println(JSON.toJSONString(resultMap));
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.testBuildTree();
        test.testGetChildInfos();
        test.testGetUpFullLinkByChildIds();
    }


    /**
     * mock树形数据
     *
     * @return
     */
    public List<TreeBuildResponseResult> mockData() {
        List<TreeBuildResponseResult> results = new ArrayList<>();
        results.add(buildEntity(1l, -1l, "郑州市", "410100"));

        results.add(buildEntity(2l, 1l, "金水区", "410105"));
        results.add(buildEntity(3l, 1l, "管城回族区", "410104"));
        results.add(buildEntity(4l, 1l, "经开区", "410171"));

        results.add(buildEntity(5l, 2l, "经八路街道", "410105001"));
        results.add(buildEntity(6l, 2l, "花园路街道", "410105002"));

        results.add(buildEntity(7l, 3l, "明湖街道", "410171560"));
        results.add(buildEntity(8l, 3l, "潮河街道", "410171561"));
        return results;
    }


    private TreeBuildResponseResult buildEntity(Long id, Long pid, String name, String code) {
        TreeBuildResponseResult build = TreeBuildResponseResult.builder().id(id)
                .pid(pid)
                .name(name)
                .code(code).build();
        return build;
    }


}

树构造结果展示如下:

java 组装树 java树形结构封装_数据库


根据父ids向下查询所有子节点信息:

java 组装树 java树形结构封装_开发语言_02


根据子节点ids向上查询所有父节点信息:

java 组装树 java树形结构封装_java 组装树_03