设备目录树--个人笔记
目录
1、前言
2、代码解释(基础版)
3、代码解释(高阶版)
4、代码解释(拓展)
1、前言
大家是否也和我一样,对目录树的实现,每次在写的时候都会感觉到头疼,甚至无从下手,那么今天就让我来介绍一下,类似这种实现设备树的做法吧。
1、首先我们要清楚的一件是这种需求的一个数据结构是什么?其实,说大实话他无非就是树的一个集合,以每一棵树的root的地址引用存储在collection集合中,然后每个root下面在生成不同的树形连接,但是他是区分于二叉树的存在,因为他只是一个单纯的树,没有涉及到左右子节点。或者说他的当前层的子节点数量不会限制为1-2个,而是0和无限多个。因此在计算机的研究上面,我们可以称他为森林。以下就是这个的一个可视化图片,大家可以试着理解一下。而在其中的红色线就相当于我们的集合,存储的则是我们的根节点引用信息。就好比如上面图片显示的每个部门的信息,然后部门其下的信息则是存储在每一个树各自的子节点中。
2、代码解释(基础版)
好的,说了那么多概念,先让我们来看一下实现这个功能的源码吧(基础版)
@Overridepublic List<DevicePlaceVo> getDevicePlaceTree() {List<DevicePlaceVo> result = new ArrayList<>();List<DevicePlace> devicePlaces = devicePlaceMapper.selectList(null);//1、 查询全部父类根节点List<DevicePlace> dpParents = devicePlaces.stream().filter((devicePlace) -> {return devicePlace.getParentId() == 0;}).collect(Collectors.toList());// 2、 根据父类查询其下的子节点Iterator<DevicePlace> iterator = dpParents.iterator();while (iterator.hasNext()) {DevicePlace dpParent = iterator.next();//1、 查询当前父类的子节点集合DevicePlaceVo vo = new DevicePlaceVo();BeanUtils.copyProperties(dpParent, vo);List<DevicePlaceVo> tempRes = new ArrayList<>();getChild(dpParent, devicePlaces, tempRes);vo.setChildren(tempRes);result.add(vo);}return result;}public void getChild(DevicePlace dpParent, List<DevicePlace> devicePlaces, List<DevicePlaceVo> result) {for (DevicePlace targetItem : devicePlaces) {if (dpParent.getTid().equals(targetItem.getParentId())) {DevicePlaceVo vo = new DevicePlaceVo();BeanUtils.copyProperties(targetItem, vo);result.add(vo);List<DevicePlaceVo> tempRes = new ArrayList<>();getChild(targetItem, devicePlaces, tempRes);vo.setChildren(tempRes);}}}
下面就让我简单的来阐述一下这个代码是怎么实现获取设备列表(森林)这个集合信息的。
(1)创建一个空集合,然后通过Mabits-Plus调用已有的方法,然后封装到存储的集合中去。
(2)这里使用了Stream,统一将root节点的(parentId)不为0的,重新打包一份放置到新的列表中去
(3)然后我们在使用迭代器来遍历父节点,并把其下的子节点按照递归的方式一一加入其中
(4)当然这里还需要对实体进行一次Vo的转换,完成之后我们调用getChild()方法,并将其初始化的空子节点,传入,待完成后,并赋值于vo的children属性,然后再将vo插入到list中去。
(5)下面重点看getChild()方法,在这个方法中,我们接受了父节点参数,和数据库查询出来的全列表数据,然后并传入一个新的子集合,用来关联子类信息。
(6) 在这个递归中,我们要注意的是,每次递归之前要先初始化一个新的子类集合,这样就不会重合在一起了。
(7) 以下就是返回结果了
{"data": [{"children": [{"children": [],"parentId": 6,"placeCode": "PE1354510120008024064","placeName": "仓库-C1","tid": 38},{"children": [{"children": [],"parentId": 64,"placeCode": "PE1354511014451740672","placeName": "仓库-C2","tid": 39}],"parentId": 6,"placeCode": "PE1362464502116777984","placeName": "仓库-C3","tid": 64}],"parentId": 0,"placeCode": "PE1353728567518691328","placeName": "仓库","tid": 6},{"children": [{"children": [],"parentId": 7,"placeCode": "PE1354513660751380480","placeName": "办公室-B1","tid": 40},{"children": [],"parentId": 7,"placeCode": "PE1354516059855519744","placeName": "仓库-C1","tid": 41}],"parentId": 0,"placeCode": "PE1353728672665698304","placeName": "办公室","tid": 7},{"children": [{"children": [],"parentId": 8,"placeCode": "PE1353732949987557376","placeName": "卸货点-S2","tid": 27},{"children": [],"parentId": 8,"placeCode": "PE1353735392796344320","placeName": "卸货点-S3","tid": 28},{"children": [],"parentId": 8,"placeCode": "PE1353761977041682432","placeName": "卸货点-S1","tid": 30},{"children": [],"parentId": 8,"placeCode": "PE1353763122304778240","placeName": "卸货点-S22","tid": 31},{"children": [],"parentId": 8,"placeCode": "PE1353763237903990784","placeName": "卸货点-S22-1","tid": 32},{"children": [],"parentId": 8,"placeCode": "PE1353763323748810752","placeName": "卸货点-S22-1-1","tid": 33},{"children": [],"parentId": 8,"placeCode": "PE1353763356061728768","placeName": "卸货点-S22-1-1-1","tid": 34},{"children": [{"children": [],"parentId": 60,"placeCode": "PE1367083893676572672","placeName": "Asd2","tid": 70}],"parentId": 8,"placeCode": "PE1362017467458650112","placeName": "卸货点-S4","tid": 60},{"children": [{"children": [],"parentId": 67,"placeCode": "PE1367083818598531072","placeName": "卸货点-A1","tid": 69}],"parentId": 8,"placeCode": "PE1367083536057630720","placeName": "卸货点-S7","tid": 67},{"children": [],"parentId": 8,"placeCode": "PE1367083685869780992","placeName": "卸货点-A7","tid": 68}],"parentId": 0,"placeCode": "PE1353728715833475072","placeName": "卸货点","tid": 8},{"children": [],"parentId": 0,"placeCode": "PE1362466704721969152","placeName": "测试点","tid": 65}]}
3、代码解释(高阶版)
说到这里不知道大家是否能看出,这个是否能优化呢,是的,这里存在的问题就是,我们每次去寻找子节点都是在数据库表返回的集合中去遍历去获取,那么在这种情况下的递归是十分缓慢的。因此我们可以有以下对应的优化。使用 Map 构建树结构!(高阶版)
@Override
public List<DevicePlaceVo> getDevicePlaceTree() {List<DevicePlace> devicePlaces = devicePlaceMapper.selectList(null);// 按 parentId 分组,提升查找效率Map<Long, List<DevicePlace>> parentIdMap = devicePlaces.stream().collect(Collectors.groupingBy(DevicePlace::getParentId));List<DevicePlaceVo> result = new ArrayList<>();List<DevicePlace> roots = parentIdMap.getOrDefault(0L, Collections.emptyList());for (DevicePlace root : roots) {DevicePlaceVo vo = convertToVo(root);buildTree(vo, parentIdMap);result.add(vo);}return result;
}private void buildTree(DevicePlaceVo parentVo, Map<Long, List<DevicePlace>> parentIdMap) {List<DevicePlace> children = parentIdMap.getOrDefault(parentVo.getTid(), Collections.emptyList());List<DevicePlaceVo> childVos = new ArrayList<>();for (DevicePlace child : children) {DevicePlaceVo childVo = convertToVo(child);buildTree(childVo, parentIdMap); // 递归构建子树childVos.add(childVo);}parentVo.setChildren(childVos);
}private DevicePlaceVo convertToVo(DevicePlace dp) {DevicePlaceVo vo = new DevicePlaceVo();BeanUtils.copyProperties(dp, vo);return vo;
}
(1)首选还是通过数据库表进行查找,但是在这里比上面多操作了一个步骤,那就是将数据以parentId进行分组,以tid为key,实体列表集合为value 存储到map集合中去了。
(2)然后就是在map集合中统一提取List集合,并初始化Vo集合,进行对象的转换。
(3)在buildTree方法中,传入当前的父类实例,以及分组好的Map集合,方法体内,根据父类的tId,获取其子类集合,然后再对获取到子类集合进行遍历,并使用递归的方式继续遍历,然后一步步的赋值即可。
4、代码解释(拓展)
看完这个,再让我们来看一下这个的进一步的优化吧,这里相当是抽象出来了一个方法
/*** 构造树列表** @param treeVoList 树列表* @param id 根节点id* @param <T> 泛型* @return 构造树*/public static <T extends TreeVo> List<T> buildTree(List<T> treeVoList, String id) {List<T> resultList = new ArrayList<>();// 如果id为空,则使用默认的树节点if (StringUtils.isEmpty(id) || StringUtils.isBlank(id)) {for (T nodeTree : treeVoList) {if (nodeTree.getParentId() == null || nodeTree.getParentId().isEmpty()) {buildChildrenTree(treeVoList, nodeTree);resultList.add(nodeTree);}}} else {// 当前节点是否为树节点for (T nodeTree : treeVoList) {if (id.equals(nodeTree.getId())) {buildChildrenTree(treeVoList, nodeTree);resultList.add(nodeTree);}}}return resultList;}/*** 构造树列表** @param treeVoList 树列表* @param parentTreeVo 父节点* @param <T> 泛型* @return*/public static <T extends TreeVo> void buildChildrenTree(List<T> treeVoList, T parentTreeVo) {for (T nodeTree : treeVoList) {if (parentTreeVo.getId().equals(nodeTree.getParentId())) {nodeTree.setParentText(parentTreeVo.getText());buildChildrenTree(treeVoList, nodeTree);parentTreeVo.setHasChildren(true);if (Objects.isNull(parentTreeVo.getChildNodes())) {parentTreeVo.setChildNodes(new ArrayList<>());}parentTreeVo.getChildNodes().add(nodeTree);}}}
(1)在这个封装的工具类中,我们强调了返回的集合的元素必须是继承TreeVo的,然后再我们传入的参数中,我们传入了查询数据表信息的集合(同上)
(2)在buildTree中,传入原始集合和id(主要是父类id),当然如果id为空,这里会使用其默认的树节点,也就是当前遍历节点的父节点和传入节点的id尽心比较,然后后面的话,主要还是和上面的步骤差不多,这里就不过多赘述了。