mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
:trollface: 优化toTree性能,抽取到CollectorUtil中
This commit is contained in:
parent
df748856d7
commit
41ce120da5
@ -1,14 +1,12 @@
|
|||||||
package cn.hutool.core.stream;
|
package cn.hutool.core.stream;
|
||||||
|
|
||||||
import cn.hutool.core.lang.Opt;
|
import cn.hutool.core.lang.Opt;
|
||||||
|
import cn.hutool.core.lang.mutable.MutableObj;
|
||||||
import cn.hutool.core.text.StrUtil;
|
import cn.hutool.core.text.StrUtil;
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.*;
|
||||||
import java.util.function.BinaryOperator;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.stream.Collector;
|
import java.util.stream.Collector;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -371,4 +369,131 @@ public class CollectorUtil {
|
|||||||
public static <K, V> Collector<Map.Entry<K, V>, ?, Map<K, V>> entryToMap() {
|
public static <K, V> Collector<Map.Entry<K, V>, ?, Map<K, V>> entryToMap() {
|
||||||
return toMap(Map.Entry::getKey, Map.Entry::getValue);
|
return toMap(Map.Entry::getKey, Map.Entry::getValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>将集合转换为树,默认用 {@code parentId == null} 来判断树的根节点,内置一个递归,注意内存开销
|
||||||
|
* 因为需要在当前传入数据里查找,所以这是一个结束操作 <br>
|
||||||
|
*
|
||||||
|
* @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId}
|
||||||
|
* @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId}
|
||||||
|
* @param childrenSetter children的setter对应的lambda,可以写作{ @code Student::setChildren}
|
||||||
|
* @param isParallel 是否并行去组装,数据量特别大时使用
|
||||||
|
* @param <T> 此处是元素类型
|
||||||
|
* @param <R> 此处是id、parentId的泛型限制
|
||||||
|
* @return list 组装好的树 <br>
|
||||||
|
* eg:
|
||||||
|
* <pre>{@code
|
||||||
|
* List<Student> studentTree = students.stream().collect(toTree(Student::getId, Student::getParentId, Student::setChildren, isParallel));
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
public static <R extends Comparable<R>, T> Collector<T, ?, List<T>> toTree(
|
||||||
|
final Function<T, R> idGetter,
|
||||||
|
final Function<T, R> pIdGetter,
|
||||||
|
final BiConsumer<T, List<T>> childrenSetter,
|
||||||
|
final boolean isParallel) {
|
||||||
|
return toTree(idGetter, pIdGetter, null, childrenSetter, isParallel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>将集合转换为树,默认用 {@code parentId == pidValue} 来判断树的根节点,可以为null,内置一个递归,注意内存开销
|
||||||
|
* 因为需要在当前传入数据里查找,所以这是一个结束操作 <br>
|
||||||
|
*
|
||||||
|
* @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId}
|
||||||
|
* @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId}
|
||||||
|
* @param childrenSetter children的setter对应的lambda,可以写作{ @code Student::setChildren}
|
||||||
|
* @param isParallel 是否并行去组装,数据量特别大时使用
|
||||||
|
* @param <T> 此处是元素类型
|
||||||
|
* @param <R> 此处是id、parentId的泛型限制
|
||||||
|
* @return list 组装好的树 <br>
|
||||||
|
* eg:
|
||||||
|
* <pre>{@code
|
||||||
|
* List<Student> studentTree = students.stream().collect(toTree(Student::getId, Student::getParentId, 0L, Student::setChildren, isParallel));
|
||||||
|
* }</pre>
|
||||||
|
* @author VampireAchao
|
||||||
|
*/
|
||||||
|
public static <R extends Comparable<R>, T> Collector<T, ?, List<T>> toTree(
|
||||||
|
final Function<T, R> idGetter,
|
||||||
|
final Function<T, R> pIdGetter,
|
||||||
|
final R pidValue,
|
||||||
|
final BiConsumer<T, List<T>> childrenSetter,
|
||||||
|
final boolean isParallel) {
|
||||||
|
return Collectors.collectingAndThen(groupingBy(pIdGetter, Collectors.toList()),
|
||||||
|
getChildrenFromMapByPidAndSet(idGetter, pIdValuesMap -> pIdValuesMap.get(pidValue), childrenSetter, isParallel));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将集合转换为树,自定义根节点的判断条件,内置一个递归,注意内存开销
|
||||||
|
* 因为需要在当前传入数据里查找,所以这是一个结束操作
|
||||||
|
*
|
||||||
|
* @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId}
|
||||||
|
* @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId}
|
||||||
|
* @param childrenSetter children的setter对应的lambda,可以写作 {@code Student::setChildren}
|
||||||
|
* @param parentPredicate 树顶部的判断条件,可以写作 {@code s -> Objects.equals(s.getParentId(),0L) }
|
||||||
|
* @param <T> 此处是元素类型
|
||||||
|
* @param <R> 此处是id、parentId的泛型限制
|
||||||
|
* @return list 组装好的树 <br>
|
||||||
|
* eg:
|
||||||
|
* <pre>{@code
|
||||||
|
* List<Student> studentTree = EasyStream.of(students).
|
||||||
|
* .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
|
||||||
|
* }</pre>
|
||||||
|
* @author VampireAchao
|
||||||
|
*/
|
||||||
|
public static <R extends Comparable<R>, T> Collector<T, ?, List<T>> toTree(
|
||||||
|
final Function<T, R> idGetter,
|
||||||
|
final Function<T, R> pIdGetter,
|
||||||
|
final BiConsumer<T, List<T>> childrenSetter,
|
||||||
|
final Predicate<T> parentPredicate,
|
||||||
|
boolean isParallel) {
|
||||||
|
List<T> parents = new ArrayList<>();
|
||||||
|
return Collectors.collectingAndThen(groupingBy(pIdGetter,
|
||||||
|
new SimpleCollector<>(ArrayList::new,
|
||||||
|
(acc, e) -> {
|
||||||
|
if (parentPredicate.test(e)) {
|
||||||
|
parents.add(e);
|
||||||
|
}
|
||||||
|
acc.add(e);
|
||||||
|
},
|
||||||
|
(left, right) -> {
|
||||||
|
left.addAll(right);
|
||||||
|
return left;
|
||||||
|
},
|
||||||
|
CH_ID)),
|
||||||
|
getChildrenFromMapByPidAndSet(idGetter, pIdValuesMap -> parents, childrenSetter, isParallel));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toTree的内联函数,内置一个小递归(没错,lambda可以写递归)
|
||||||
|
* 因为需要在当前传入数据里查找,所以这是一个结束操作
|
||||||
|
*
|
||||||
|
* @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId}
|
||||||
|
* @param parentFactory 顶部数据工厂方法
|
||||||
|
* @param childrenSetter children的setter对应的lambda,可以写作 {@code Student::setChildren}
|
||||||
|
* @param isParallel 是否并行处理
|
||||||
|
* @param <T> 此处是元素类型
|
||||||
|
* @param <R> 此处是id的泛型限制
|
||||||
|
* @return list 组装好的树
|
||||||
|
* @author VampireAchao
|
||||||
|
*/
|
||||||
|
private static <R extends Comparable<R>, T> Function<Map<R, List<T>>, List<T>> getChildrenFromMapByPidAndSet(
|
||||||
|
final Function<T, R> idGetter,
|
||||||
|
final Function<Map<R, List<T>>, List<T>> parentFactory,
|
||||||
|
final BiConsumer<T, List<T>> childrenSetter,
|
||||||
|
boolean isParallel) {
|
||||||
|
return pIdValuesMap -> {
|
||||||
|
final MutableObj<Consumer<List<T>>> recursiveRef = new MutableObj<>();
|
||||||
|
final Consumer<List<T>> recursive = parents -> EasyStream.of(parents, isParallel).forEach(parent -> {
|
||||||
|
final List<T> children = pIdValuesMap.get(idGetter.apply(parent));
|
||||||
|
childrenSetter.accept(parent, children);
|
||||||
|
recursiveRef.get().accept(children);
|
||||||
|
});
|
||||||
|
List<T> parents = parentFactory.apply(pIdValuesMap);
|
||||||
|
if (!parents.isEmpty()) {
|
||||||
|
recursiveRef.set(recursive);
|
||||||
|
recursiveRef.get().accept(parents);
|
||||||
|
}
|
||||||
|
return parents;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,7 @@ import cn.hutool.core.lang.Opt;
|
|||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
import java.util.function.*;
|
import java.util.function.*;
|
||||||
@ -267,7 +265,7 @@ public class EasyStream<T> extends AbstractEnhancedWrappedStream<T, EasyStream<T
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>将集合转换为树,默认用 {@code parentId == null} 来判断树的根节点
|
* <p>将集合转换为树,默认用 {@code parentId == null} 来判断树的根节点,内置一个递归,注意内存开销
|
||||||
* 因为需要在当前传入数据里查找,所以这是一个结束操作 <br>
|
* 因为需要在当前传入数据里查找,所以这是一个结束操作 <br>
|
||||||
*
|
*
|
||||||
* @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId}
|
* @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId}
|
||||||
@ -280,18 +278,17 @@ public class EasyStream<T> extends AbstractEnhancedWrappedStream<T, EasyStream<T
|
|||||||
* List<Student> studentTree = EasyStream.of(students).
|
* List<Student> studentTree = EasyStream.of(students).
|
||||||
* toTree(Student::getId, Student::getParentId, Student::setChildren);
|
* toTree(Student::getId, Student::getParentId, Student::setChildren);
|
||||||
* }</pre>
|
* }</pre>
|
||||||
|
* @author VampireAchao
|
||||||
*/
|
*/
|
||||||
public <R extends Comparable<R>> List<T> toTree(
|
public <R extends Comparable<R>> List<T> toTree(
|
||||||
final Function<T, R> idGetter,
|
final Function<T, R> idGetter,
|
||||||
final Function<T, R> pIdGetter,
|
final Function<T, R> pIdGetter,
|
||||||
final BiConsumer<T, List<T>> childrenSetter) {
|
final BiConsumer<T, List<T>> childrenSetter) {
|
||||||
// 使用 parentId == null 判断是否为根节点
|
return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, isParallel()));
|
||||||
final Predicate<T> parentPredicate = node -> null == pIdGetter.apply(node);
|
|
||||||
return toTree(idGetter, pIdGetter, childrenSetter, parentPredicate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将集合转换为树,自定义根节点的判断条件
|
* 将集合转换为树,自定义根节点的判断条件,内置一个递归,注意内存开销
|
||||||
* 因为需要在当前传入数据里查找,所以这是一个结束操作
|
* 因为需要在当前传入数据里查找,所以这是一个结束操作
|
||||||
*
|
*
|
||||||
* @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId}
|
* @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId}
|
||||||
@ -305,39 +302,14 @@ public class EasyStream<T> extends AbstractEnhancedWrappedStream<T, EasyStream<T
|
|||||||
* List<Student> studentTree = EasyStream.of(students).
|
* List<Student> studentTree = EasyStream.of(students).
|
||||||
* .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
|
* .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
|
||||||
* }</pre>
|
* }</pre>
|
||||||
|
* @author VampireAchao
|
||||||
*/
|
*/
|
||||||
public <R extends Comparable<R>> List<T> toTree(
|
public <R extends Comparable<R>> List<T> toTree(
|
||||||
final Function<T, R> idGetter,
|
final Function<T, R> idGetter,
|
||||||
final Function<T, R> pIdGetter,
|
final Function<T, R> pIdGetter,
|
||||||
final BiConsumer<T, List<T>> childrenSetter,
|
final BiConsumer<T, List<T>> childrenSetter,
|
||||||
final Predicate<T> parentPredicate) {
|
final Predicate<T> parentPredicate) {
|
||||||
Objects.requireNonNull(idGetter);
|
return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, parentPredicate, isParallel()));
|
||||||
Objects.requireNonNull(pIdGetter);
|
|
||||||
Objects.requireNonNull(childrenSetter);
|
|
||||||
Objects.requireNonNull(parentPredicate);
|
|
||||||
|
|
||||||
List<T> nodeList = toList();
|
|
||||||
// 根据 父id 分组,让key为null的组中全是根节点
|
|
||||||
final Function<T, R> pIdClassifier = node -> {
|
|
||||||
// 该节点是根节点, 分到 父id 为null的组中
|
|
||||||
if (parentPredicate.test(node)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 返回 父id
|
|
||||||
return pIdGetter.apply(node);
|
|
||||||
};
|
|
||||||
// 父id 关联的 子节点列表
|
|
||||||
final Map<R, List<T>> pId2ChildrenMap = of(nodeList).group(pIdClassifier);
|
|
||||||
|
|
||||||
of(nodeList, true).forEach(node -> {
|
|
||||||
// 设置 该节点的子节点列表
|
|
||||||
final List<T> children = pId2ChildrenMap.get(idGetter.apply(node));
|
|
||||||
if (children != null) {
|
|
||||||
childrenSetter.accept(node, children);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// 返回根节点列表
|
|
||||||
return pId2ChildrenMap.getOrDefault(null, Collections.emptyList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -477,7 +477,7 @@ public class EasyStreamTest {
|
|||||||
Student.builder().id(8L).name("jobob").parentId(5L).build()
|
Student.builder().id(8L).name("jobob").parentId(5L).build()
|
||||||
)
|
)
|
||||||
// just 4 lambda ,top by condition
|
// just 4 lambda ,top by condition
|
||||||
.toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
|
.toTree(Student::getId, Student::getParentId, Student::setChildren, s -> BooleanUtil.isTrue(s.getMatchParent()));
|
||||||
Assert.assertEquals(asList(
|
Assert.assertEquals(asList(
|
||||||
Student.builder().id(1L).name("dromara").matchParent(true)
|
Student.builder().id(1L).name("dromara").matchParent(true)
|
||||||
.children(asList(Student.builder().id(3L).name("hutool").parentId(1L)
|
.children(asList(Student.builder().id(3L).name("hutool").parentId(1L)
|
||||||
@ -547,10 +547,6 @@ public class EasyStreamTest {
|
|||||||
public Student() {
|
public Student() {
|
||||||
// this is an accessible parameterless constructor.
|
// this is an accessible parameterless constructor.
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean getMatchParent() {
|
|
||||||
return BooleanUtil.isTrue(matchParent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
Loading…
x
Reference in New Issue
Block a user