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());
}
}