Java 列表转树状结构 list to tree

利用 stream 实现 List 转树状结构的两种方法

     先构造一个节点类 包含必须属性 3个

/**
 * @author zjt
 * @date 2022-10-12
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Node {

    private Integer id;
	
	/**
     * 指向父id
     */
    private Integer parentId;

    private String name;

    private List<Node> childList;

}
方法一

     方法一: 利用stream + 递归的方式去实现
     缺点 使用递归 数据量大时可能会造成 StackOverflowError 错误。(线程请求分配的栈容量超过Java虚拟机栈允许的最大容量)
     时间复杂度大,数据量多、层级深时不建议使用
     经过自测 小于1000条数据且转换过后树的高度不超过 5 可以使用 。(未构造树的根节点)

	/**
     * stream + 递归 的方式 构造树形结构
     *
     * @param nodes 源数据列表
     * @return 树形结构列表
     */
    public static List<Node> listToTree1(List<Node> nodes) {
        return nodes.stream()
                // 获取一级树形结构
                .filter(item -> item.getParentId().equals(0))
                .peek(item -> {
                    // 赋值子列表
                    item.setChildList(getNodeChildList(item.getId(), nodes));
                }).collect(Collectors.toList());
    }

    public static List<Node> getNodeChildList(Integer parentId, List<Node> nodes) {
        return nodes.stream()
                .filter(item -> Objects.equals(item.getParentId(), parentId))
                .peek(item -> {
                    // 递归获取子列表
                    item.setChildList(getNodeChildList(item.getId(), nodes));
                }).collect(Collectors.toList());
    }
方法二

     方法二: 利用stream + for 循环 实现List 转树状结构
     相比方法一 速度更快

    /**
     * 方法二  利用 stream 的 groupingBy 方法 转map 然后实现列表转树状结构
     */
    public static List<Node> listToTree2(List<Node> nodes) {
        Map<Integer, List<Node>> nodeByPidMap = nodes.stream().collect(Collectors.groupingBy(Node::getParentId));
        // 循环一次设置当前节点的子节点
        nodes.forEach(node -> node.setChildList(nodeByPidMap.get(node.getId())));
        // 获取 一级列表
        return nodes.stream().filter(node -> Objects.equals(node.getParentId(), 0)).collect(Collectors.toList());
    }
测试
    public static void main(String[] args) throws NoSuchFieldException {
        // 构造100万条数据 深度是6
        List<Node> nodes = new ArrayList<>();
        for (int i = 0; i < 1_000_000; i++) {
            int remainder = i % 6;
            if (remainder == 0) {
                nodes.add(new Node(i + 1, 0, String.valueOf(i), null));
            } else {
                nodes.add(new Node(i + 1, i - 1, String.valueOf(i), null));
            }
        }
        long l3 = System.currentTimeMillis();
        List<Node> nodeList2 = listToTree2(nodes);
        System.out.println("方法二  利用 stream 的 groupingBy 方法 转map 所需时间" + (System.currentTimeMillis() - l3));

        long l2 = System.currentTimeMillis();
        List<Node> nodeList1 = listToTree1(nodes);
        System.out.println("方法一 stream + 递归 的方式 构造树形结构 " + (System.currentTimeMillis() - l2));

    }

     测试结果 方法一已经超过栈的最大可达深度 必然报错
在这里插入图片描述
     将条件改为 构造10万条数据 深度是5
在这里插入图片描述

改造

     对于需求来说,使用最多的列表转树形结构还是在层级部门或者菜单,数据量并不是很大,在此基础上将方式一 修改为通用的工具类(数据量多时转换还是使用方法二,方法一不可行,在方法一基础上的改造更不可行)。


import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 递归测试
 *
 * @author zjt
 * @date 2022-10-12
 */
public class ListToTreeUtil {


    /**
     * 指向父级的id 类型必须是int
     */
    private static final String PARENT_FILED_NAME = "parentId";

    /**
     * 子集列表
     */
    private static final String CHILD_FILED_NAME = "childList";

    /**
     * 本身的id
     */
    private static final String ID_FILED_NAME = "id";


    /**
     * 列表转树形结构
     *
     * @param tList 转换前的列表
     * @param <T>   范型 T 需要保证转换的类中对应关系 Integer类型的 parentId 与指向父级的 id  保证类型中包含 List<T> 的 childList 属性
     * @return 转换后的列表
     */
    public static <T> List<T> transferListToTree(List<T> tList) throws NoSuchFieldException {
        if (CollectionUtils.isEmpty(tList)) {
            return tList;
        }
        return transferListToTree(tList, ID_FILED_NAME, PARENT_FILED_NAME, CHILD_FILED_NAME);

    }

    public static <T> List<T> transferListToTree(List<T> tList, String idFieldName, String pidFieldName, String childListFieldName) throws NoSuchFieldException {
        if (CollectionUtils.isEmpty(tList)) {
            return tList;
        }
        if (StringUtils.isEmpty(idFieldName) || StringUtils.isEmpty(pidFieldName) || StringUtils.isEmpty(childListFieldName)) {
            throw new NoSuchFieldException("属性名称缺失");
        }
        return tList.stream().filter(item -> {
            try {
                // 获取父级节点
                Field field = item.getClass().getDeclaredField(pidFieldName);
                field.setAccessible(Boolean.TRUE);
                Integer pid = (Integer) field.get(item);
                return pid == 0;
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new RuntimeException("树形结构转换失败");
            }
        }).map(item -> {
            try {
                Field childField = item.getClass().getDeclaredField(childListFieldName);
                childField.setAccessible(Boolean.TRUE);
                childField.set(item, getChildList(item, tList, idFieldName, pidFieldName, childListFieldName));
                return item;
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new RuntimeException("树形结构转换失败");
            }
        }).collect(Collectors.toList());
    }

    private static <T> List<T> getChildList(T t, List<T> tList, String idFieldName, String pidFieldName, String childListFieldName) throws NoSuchFieldException, IllegalAccessException {
        Field parentField = t.getClass().getDeclaredField(idFieldName);
        parentField.setAccessible(Boolean.TRUE);
        Integer id = (Integer) parentField.get(t);
        return tList.stream().filter(item -> {
            try {
                // 获取该节点下的所有子节点
                Field field = item.getClass().getDeclaredField(pidFieldName);
                field.setAccessible(Boolean.TRUE);
                Integer pid = (Integer) field.get(item);
                return pid.equals(id);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new RuntimeException("树形结构转换失败");
            }
        }).peek(item -> {
            try {
                Field childField = item.getClass().getDeclaredField(childListFieldName);
                childField.setAccessible(Boolean.TRUE);
                childField.set(item, getChildList(item, tList, idFieldName, pidFieldName, childListFieldName));
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new RuntimeException("树形结构转换失败");
            }
        }).collect(Collectors.toList());
    }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值