树结构通用封装
背景:在写代码的过程中,经常有些数据是树形结构的数据,如:常见的组织数据,区划信息,以及经常操作的文件夹等等… 但是数据库中存储的数据都是按照行进行存储,用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;
}
}
树构造结果展示如下:
根据父ids向下查询所有子节点信息:
根据子节点ids向上查询所有父节点信息: