9.10

为了解决一个层次树转父子树的问题,实验了一周,稍微总结一下为什么失败了这么多次.
首先数据库里有这样一张表嗯,然后主键唯一的macid对应唯一macname,每个macid有一个可以重复的macpath.

image.png
表的行类似于

  1. {
  2. "macid": "114514"
  3. "macname": "下北泽"
  4. "macpath": "亚欧大陆/中原/大吴疆土/合肥"
  5. }

然后前端页面需要传递给element-ui的tree组件的格式和这个完全不一样.前端给一个macname过来后端数据库根据macpath模糊查询符合条件的所有macname,macpath.
image.png
对于这种路径不定的macpath,我比较尴尬,我没有处理这种层次树的经验,以前都是处理那种有父节点id的二维表.
然后我的第一种方案是先for一下,但是这样是查出来了,没有实现同一父节点的合并.

  1. //下为OverViewAriesBO.java
  2. @Data
  3. @AllArgsConstructor
  4. @NoArgsConstructor
  5. @Builder
  6. public class OverViewAriesBO {
  7. private String id;
  8. private String label;
  9. private List<OverViewAriesBO> children;
  10. }
  11. //MachinesPO就是PO,对数据库的表完整映射
  12. // 下为OverViewServiceImpl.java
  13. //服务于element的tree组件的格式要求
  14. @Override
  15. public List<OverViewAriesBO> findPathTree(String company) {
  16. List<MachinesPO> path = overViewMapper.findPathTree(company);
  17. List<OverViewAriesBO> overViewAriesBOS = new ArrayList<>();
  18. for (MachinesPO machinesPO : path) {
  19. OverViewAriesBO firstBo = OverViewAriesBO
  20. .builder()
  21. .label(machinesPO.getMacpath().split("/", 3)[0])
  22. .build();
  23. overViewAriesBOS.add(firstBo);
  24. OverViewAriesBO secondBo = OverViewAriesBO
  25. .builder()
  26. .label(machinesPO.getMacpath().split("/", 3)[1])
  27. .build();
  28. firstBo.setChildren(new ArrayList<OverViewAriesBO>() {{
  29. add(secondBo);
  30. }});
  31. OverViewAriesBO thirdBo = OverViewAriesBO
  32. .builder()
  33. .label(machinesPO.getMacpath().split("/", 3)[2])
  34. .build();
  35. secondBo.setChildren(new ArrayList<OverViewAriesBO>() {{
  36. add(thirdBo);
  37. }});
  38. OverViewAriesBO finallyBo = OverViewAriesBO
  39. .builder()
  40. .id(machinesPO.getMacid())
  41. .label(machinesPO.getMacname())
  42. .children(null)
  43. .build();
  44. thirdBo.setChildren(new ArrayList<OverViewAriesBO>() {{
  45. add(finallyBo);
  46. }});
  47. }
  48. return overViewAriesBOS;
  49. }
  50. }
  1. //别尬黑,我改过json数据了
  2. [
  3. {
  4. "id": null,
  5. "label": "叔叔我啊叔叔我啊",
  6. "children": [
  7. {
  8. "id": null,
  9. "label": "真的要生气了",
  10. "children": [
  11. {
  12. "id": null,
  13. "label": "红萝卜",
  14. "children": [
  15. {
  16. "id": "1232864287364293",
  17. "label": "PA11",
  18. "children": null
  19. }
  20. ]
  21. }
  22. ]
  23. }
  24. ]
  25. },
  26. {
  27. "id": null,
  28. "label": "叔叔我啊叔叔我啊",
  29. "children": [
  30. {
  31. "id": null,
  32. "label": "真的要生气了",
  33. "children": [
  34. {
  35. "id": null,
  36. "label": "红萝卜",
  37. "children": [
  38. {
  39. "id": "23426346329232",
  40. "label": "PA12",
  41. "children": null
  42. }
  43. ]
  44. }
  45. ]
  46. }
  47. ]
  48. },
  49. {
  50. "id": null,
  51. "label": "叔叔我啊叔叔我啊",
  52. "children": [
  53. {
  54. "id": null,
  55. "label": "真的要生气了",
  56. "children": [
  57. {
  58. "id": null,
  59. "label": "红萝卜",
  60. "children": [
  61. {
  62. "id": "23462364328746329",
  63. "label": "PTA2汽轮发电机组",
  64. "children": null
  65. }
  66. ]
  67. }
  68. ]
  69. }
  70. ]
  71. },
  72. {
  73. "id": null,
  74. "label": "叔叔我啊叔叔我啊",
  75. "children": [
  76. {
  77. "id": null,
  78. "label": "真的要生气了",
  79. "children": [
  80. {
  81. "id": null,
  82. "label": "红萝卜/白萝卜",
  83. "children": [
  84. {
  85. "id": "234234345456",
  86. "label": "PA2A",
  87. "children": null
  88. }
  89. ]
  90. }
  91. ]
  92. }
  93. ]
  94. },
  95. {
  96. "id": null,
  97. "label": "叔叔我啊叔叔我啊",
  98. "children": [
  99. {
  100. "id": null,
  101. "label": "真的要生气了",
  102. "children": [
  103. {
  104. "id": null,
  105. "label": "绿萝卜",
  106. "children": [
  107. {
  108. "id": "2343456556544353",
  109. "label": "PA2B",
  110. "children": null
  111. }
  112. ]
  113. }
  114. ]
  115. }
  116. ]
  117. }
  118. ]

然后我试验了一下stream流,感觉不对劲,当时写完就觉得不妥.那个版本删了.
然后我手动操作了一下树.

  1. /**
  2. * @类名称: MsgNode
  3. */
  4. @Data
  5. @NoArgsConstructor
  6. public class MsgNode implements Serializable {
  7. /**
  8. * @属性名称: serialVersionUID
  9. */
  10. private static final long serialVersionUID = 6334350503505500989L;
  11. private String fldNm; // 报文字段名称
  12. private String path; // 节点路径
  13. private List<MsgNode> children = null;
  14. public MsgNode(String path,String name) {
  15. this.path = path;
  16. this.fldNm = name;
  17. }
  18. }
  1. //服务于element的tree组件的格式要求
  2. @Override
  3. public List<MsgNode> findPathTree(String company) {
  4. List<MachinesPO> path = overViewMapper.findPathTree(company);
  5. List<MsgNode> msgNodes = pathToTree(path);
  6. return msgNodes;
  7. }
  8. public static List<MsgNode> pathToTree(List<MachinesPO> flds) {
  9. List<MsgNode> tree = new ArrayList<>();
  10. //hashset特点,HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。HashSet 允许有 null 值。HashSet 是无序的,即不会记录插入的顺序。
  11. HashSet<MsgNode> set = new HashSet<>();
  12. for (MachinesPO fld : flds) { //对MachinesPO的List进行遍历
  13. String path = fld.getMacpath();
  14. while (path.contains("/")) { //当macpath存在多级路径,进入while循环
  15. // 子节点的路径和名称
  16. int index = path.lastIndexOf("/");
  17. MsgNode childNode = new MsgNode(path, path.substring(index + 1)); //path采用全路径,以路径的最后一级作为子节点的name
  18. // 父节点的路径和名称
  19. String parPaht = path.substring(0, index);
  20. //父节点的path为子节点path的前面全路径,name为子节点name的上一级路径,确保父子节点能衔接上
  21. MsgNode parentNode = new MsgNode(parPaht, parPaht.substring(parPaht.lastIndexOf("/") + 1));
  22. if (set.contains(parentNode)) { //如果hashset里面已经存在这个parentNode父节点
  23. for (MsgNode msgNode : set) { //对hashset集合的每一个MsgNode元素遍历
  24. if (msgNode.getPath().equals(parentNode.getPath())) { //找到这个已存在的父节点的path
  25. parentNode = msgNode; //改变父节点为该值
  26. break; //退出for循环
  27. }
  28. }
  29. } else { //如果hashset里面不存在这个父节点
  30. set.add(parentNode); //将父节点加入hashset
  31. }
  32. if (set.contains(childNode)) { // 如果hashset里面已经存在这个childNode子节点
  33. for (MsgNode msgNode : set) { // 对hashset集合的每一个MsgNode元素遍历
  34. if (msgNode.getPath().equals(childNode.getPath())) { //找到这个已存在的子节点的path
  35. childNode = msgNode; //改变子节点为该值
  36. break; //退出for循环
  37. }
  38. }
  39. if (null != parentNode.getChildren()) { //如果hashset找到父节点,而且parentNode父节点存在子节点
  40. if (!parentNode.getChildren().contains(childNode)) { //如果childNode子节点的内容不等于parentNode父节点已存在子节点
  41. parentNode.getChildren().add(childNode); //parentNode父节点的子节点list加入这个新的childNode子节点
  42. }
  43. } else { // 无论hashset找没找到父节点,如果parentNode父节点不存在子节点
  44. List<MsgNode> children = new ArrayList<>();
  45. children.add(childNode);
  46. // parentNode父节点设置新的子节点list,并把childNode子节点赋给parentNode父节点的子节点list中去
  47. parentNode.setChildren(children);
  48. }
  49. } else { // 如果hashset里面不存在这个childNode子节点
  50. if (null != parentNode.getChildren()) { // 如果hashset找到父节点,而且parentNode父节点存在子节点
  51. parentNode.getChildren().add(childNode); // parentNode父节点将childNode子节点加入自己的子节点list
  52. } else { // 无论hashset找没找到父节点,如果parentNode父节点不存在子节点
  53. List<MsgNode> children = new ArrayList<>();
  54. children.add(childNode);
  55. // parentNode父节点设置新的子节点list,并把childNode子节点赋给parentNode父节点的子节点list中去
  56. parentNode.setChildren(children);
  57. }
  58. set.add(childNode); //hashset里添加这个childNode节点
  59. }
  60. path = parPaht; //让path等于parentNode父节点的path,执行下一轮while循环
  61. }
  62. } //此时才完成对MachinesPO的List的遍历
  63. for (MsgNode msgNode : set) { // 对于hashset的MsgNode进行遍历
  64. //因为hashset里面包含不重复的父节点和子节点,需要判断如果MsgNode的路径都不存在/了,即path只有单层层次,才让MsgNode进入tree
  65. if (!msgNode.getPath().contains("/")) {
  66. tree.add(msgNode); //让这个符合单层结构path的MsgNode进入List<MsgNode> tree
  67. }
  68. }
  69. return tree; //返回List<MsgNode> tree
  70. }

看起来非常不完美.蚌埠住了.

  1. [
  2. {
  3. "fldNm": "概率论",
  4. "path": "概率论",
  5. "children": [
  6. {
  7. "fldNm": "你滴勋宗无限猖狂",
  8. "path": "概率论/你滴勋宗无限猖狂",
  9. "children": null
  10. }
  11. ]
  12. },
  13. {
  14. "fldNm": "概率论",
  15. "path": "概率论",
  16. "children": [
  17. {
  18. "fldNm": "你滴勋宗无限猖狂",
  19. "path": "概率论/你滴勋宗无限猖狂",
  20. "children": [
  21. {
  22. "fldNm": "甲醇装置",
  23. "path": "概率论/你滴勋宗无限猖狂/甲醇装置",
  24. "children": null
  25. },
  26. {
  27. "fldNm": "煤气装置",
  28. "path": "概率论/你滴勋宗无限猖狂/煤气装置",
  29. "children": null
  30. }
  31. ]
  32. }
  33. ]
  34. },
  35. {
  36. "fldNm": "概率论",
  37. "path": "概率论",
  38. "children": [
  39. {
  40. "fldNm": "山西同煤广发化学工业有限公司",
  41. "path": "概率论/你滴勋宗无限猖狂",
  42. "children": [
  43. {
  44. "fldNm": "甲醇装置",
  45. "path": "概率论/你滴勋宗无限猖狂/甲醇装置",
  46. "children": null
  47. },
  48. {
  49. "fldNm": "煤气装置",
  50. "path": "概率论/你滴勋宗无限猖狂/煤气装置",
  51. "children": null
  52. }
  53. ]
  54. }
  55. ]
  56. },
  57. {
  58. "fldNm": "概率论",
  59. "path": "概率论",
  60. "children": [
  61. {
  62. "fldNm": "你滴勋宗无限猖狂",
  63. "path": "概率论/你滴勋宗无限猖狂",
  64. "children": [
  65. {
  66. "fldNm": "甲醇装置",
  67. "path": "概率论/你滴勋宗无限猖狂/甲醇装置",
  68. "children": null
  69. },
  70. {
  71. "fldNm": "煤气装置",
  72. "path": "概率论/你滴勋宗无限猖狂/煤气装置",
  73. "children": null
  74. }
  75. ]
  76. }
  77. ]
  78. }
  79. ]

写一个去除数组中的空元素的中间方法时突然意识到问题

  1. /***
  2. * 去除数组中的空元素
  3. * @author linghengqian
  4. * @date 2021/09/10 00:28
  5. * @return java.lang.String[]
  6. */
  7. private static String[] deleteArrayNull(String[] string) {
  8. // step1: 定义一个list列表,并循环赋值
  9. ArrayList<String> strList = new ArrayList<>();
  10. Collections.addAll(strList, string);
  11. // step2: 删除list列表中所有的空值
  12. strList.removeIf(s -> Objects.equals(s, "") || s == null);
  13. // step3: 把list列表转换给一个新定义的中间数组,并赋值给它
  14. return strList.toArray(new String[0]);
  15. }

将集合转换为数组有两种样式:使用预先调整大小的数组(如c.toArray(new String [c.size()]))或使用空数组(如c.toArray(new String [ 0])。在较旧的Java版本中,建议使用预先调整大小的数组,因为创建适当大小的数组所需的反射调用非常慢。但是由于OpenJDK 6的后期更新,这个调用是内在的,使得性能得以提高与预先调整大小的版本相比,空数组版本相同,有时甚至更好。同时传递预先调整大小的数组对于并发或同步集合是危险的,因为大小和toArray调用之间可能存在数据争用,这可能会导致额外的如果集合在操作期间同时收缩,则数组末尾为空。此检查允许遵循统一样式:使用空数组(在现代Java中推荐)或使用预先调整大小的数组(在旧Java版本或基于非HotSpot的JVM中可能更快)。
不会有人部署时用Eclipse OpenJ9 JVM吧,不会吧不会吧?
然后我网上又整了一套下来看,对着打注释

  1. //服务于element的tree组件的格式要求
  2. @Override
  3. public List<MachinesPO> findPathTree(String company) {
  4. List<String> fileList = Arrays.asList("192.168.3.140/SPShareFile/secondtest.txt", "192.168.3.140/SPShareFile/新建文本文档.txt", "/opt/Spinfo/testCL/typeofball.txt");
  5. List<NameToValDto> nvdListAll = new ArrayList<>(); //引入新的NameToValDto的list,名为nvdListAll
  6. fileList.forEach(f -> { //对fileList的每一个String对象,即path
  7. String[] pathesP = f.split("/"); //把path分割为若干份单路径,合为数组
  8. String[] notNullVal = deleteArrayNull(pathesP); //删除pathesP数组中的空元素,叫notNullVal
  9. List<NameToValDto> nvdList = new ArrayList<>(); //新建一个NameToValDto的临时list,叫nvdList
  10. String tagFileOnly = ""; //新建一个叫tagFileOnly的string对象
  11. for (int i = 0; i < notNullVal.length; i++) { //对无重复路径的notNullVal的每一个string对象做遍历
  12. tagFileOnly += notNullVal[i]; //对notNullVal的第i个string对象,添加到tagFileOnly尾部
  13. String topName = notNullVal[0]; //将notNullVal的第0个string对象,即最高一级路径赋给topName(string)
  14. if (i == notNullVal.length - 1) { //如果i已经是notNullVal的最后一个string对象
  15. //将i+1的值转字符串,notNullVal的第i个string对象,i的值转字符串,最后一级的判断属性(file),莫名其妙的sp,最高一级路径(notNullVal的第0个string对象),istop属性,加上notNullVal的第i个string对象后的tagFileOnly
  16. NameToValDto nvd = new NameToValDto(String.valueOf(i + 1), notNullVal[i], String.valueOf(i), "file", "sp", topName, false, tagFileOnly);
  17. // 把这个NameToValDto,add进叫nvdList的NameToValDto的临时list
  18. nvdList.add(nvd);
  19. } else if (i == 0) { //如果i是notNullVal的第0个string对象
  20. NameToValDto nvd = new NameToValDto(String.valueOf(i + 1), notNullVal[i], String.valueOf(i), "dir", "sp", topName, true, tagFileOnly);
  21. nvdList.add(nvd);
  22. } else { //如果i是notNullVal中间而非两端的string对象
  23. NameToValDto nvd = new NameToValDto(String.valueOf(i + 1), notNullVal[i], String.valueOf(i), "dir", "sp", topName, false, tagFileOnly);
  24. nvdList.add(nvd);
  25. }
  26. } //完成对notNullVal的遍历
  27. nvdListAll.addAll(nvdList); //把nvdList的NameToValDto全部放入nvdListAll中,进行下一个String的foreach遍历
  28. }); //完成fileList原始数据的foreach操作
  29. List<NameToValDto> nts = new ArrayList<>(); //制造新的NameToValDto的list,取名nts
  30. Map<String, List<NameToValDto>> ntMap = nvdListAll
  31. .stream()
  32. .collect(Collectors.groupingBy(NameToValDto::getTopName)); //制造
  33. ntMap.forEach((k, v) -> {
  34. String uuid = IdUtils.UUID();
  35. v.forEach(o -> {
  36. o.setId(uuid + o.getId());
  37. o.setPid(uuid + o.getPid());
  38. });
  39. List<NameToValDto> nv = v
  40. .stream()
  41. .collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(NameToValDto::getName))), ArrayList::new));
  42. nts.addAll(nv);
  43. });
  44. NameToValDto creatTopOne = new NameToValDto("-1", "信息源", "0");
  45. nts.forEach(s -> {
  46. if (s.isTop()) {
  47. s.setPid("-1");
  48. }
  49. });
  50. nts.add(creatTopOne);
  51. List<NameToValDto> parentNode = nts.stream().filter(x -> x.getPid().equals("0")).collect(Collectors.toList());
  52. DiGuiTree audeptTree = new DiGuiTree(parentNode, nts);
  53. List<NameToValDto> treelist = audeptTree.returnList();
  54. List<MachinesPO> path = overViewMapper.findPathTree(company);
  55. return path;
  56. }
  57. /***
  58. * 去除数组中的空元素
  59. * @date 2021/09/10 07:28
  60. * @return java.lang.String[]
  61. */
  62. private static String[] deleteArrayNull(String[] string) {
  63. // step1: 定义一个list列表,并循环赋值
  64. ArrayList<String> strList = new ArrayList<>();
  65. Collections.addAll(strList, string);
  66. // step2: 删除list列表中所有的空值
  67. strList.removeIf(s -> Objects.equals(s, "") || s == null);
  68. // step3: 把list列表转换给一个新定义的中间数组,并赋值给它
  69. return strList.toArray(new String[0]);
  70. }
  1. //没实现完....懒得实现了
  2. @Data
  3. public class NameToValDto {
  4. private String id;
  5. private String pid;
  6. private String name;
  7. private String topName;
  8. private boolean isTop;
  9. private List<NameToValDto> children;
  10. public NameToValDto(String id, String pid, String name, String topName, boolean isTop, List<NameToValDto> children) {
  11. this.id = id;
  12. this.pid = pid;
  13. this.name = name;
  14. this.topName = topName;
  15. this.isTop = isTop;
  16. this.children = children;
  17. }
  18. public NameToValDto(String valueOf, String s, String valueOf1, String file, String sp, String topName, boolean b, String tagFileOnly) {
  19. }
  20. public NameToValDto(String valueOf, String 信息源, String valueOf1) {
  21. }
  22. }
  1. @AllArgsConstructor
  2. public class DiGuiTree {
  3. /**
  4. * 父节点
  5. */
  6. private List<NameToValDto> root;
  7. /**
  8. * 所有节点(或不包含父节点)
  9. */
  10. private List<NameToValDto> body;
  11. public List<NameToValDto> returnList() {
  12. if (body != null && !body.isEmpty()) {
  13. Map<String, String> map = new HashMap<>();
  14. root.forEach(entity -> getChildren(entity, map));
  15. return root;
  16. }
  17. return null;
  18. }
  19. public void getChildren(NameToValDto tnvo, Map<String, String> map) {
  20. List<NameToValDto> children = new ArrayList<>();
  21. body.stream()
  22. .filter(entity -> !map.containsKey(entity.getId()))
  23. .filter(entity -> entity.getPid().equals(tnvo.getId()))
  24. .forEach(entity -> {
  25. map.put(entity.getId(), entity.getPid());
  26. getChildren(entity, map);
  27. children.add(entity);
  28. });
  29. tnvo.setChildren(children);
  30. }
  31. }
  1. public class IdUtils {
  2. /**
  3. * 基于UUID+MD5产生唯一无序ID
  4. * <pre>
  5. * 线程数量: 100
  6. * 执行次数: 1000
  7. * 平均耗时: 591 ms
  8. * 数组长度: 100000
  9. * Map Size: 100000
  10. * </pre>
  11. *
  12. * @return ID长度32位
  13. */
  14. public static String UUID() {
  15. return DigestUtils.md5Hex(UUID.randomUUID().toString());
  16. }
  17. /* ---------------------------------------------分割线------------------------------------------------ */
  18. /**
  19. * 字符串MD5处理类
  20. */
  21. private static class DigestUtils {
  22. private static final char[] DIGITS_LOWER =
  23. {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
  24. private static char[] encodeHex(final byte[] data) {
  25. final int l = data.length;
  26. final char[] out = new char[l << 1];
  27. for (int i = 0, j = 0; i < l; i++) {
  28. out[j++] = DigestUtils.DIGITS_LOWER[(0xF0 & data[i]) >>> 4];
  29. out[j++] = DigestUtils.DIGITS_LOWER[0x0F & data[i]];
  30. }
  31. return out;
  32. }
  33. public static String md5Hex(String str) {
  34. String md5;
  35. try {
  36. md5 = new String(encodeHex(MessageDigest.getInstance("MD5").digest(str.getBytes(StandardCharsets.UTF_8))));
  37. } catch (Exception e) {
  38. throw new RuntimeException(e);
  39. }
  40. return md5;
  41. }
  42. }
  43. }
  1. 越整越复杂,我全丢了算了,直接用递归,不考虑时间复杂度了