:trollface: 优化toTree性能,抽取到CollectorUtil中

This commit is contained in:
VampireAchao 2022-09-19 11:16:27 +08:00
parent df748856d7
commit 41ce120da5
3 changed files with 144 additions and 51 deletions

View File

@ -1,14 +1,12 @@
package cn.hutool.core.stream;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.mutable.MutableObj;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ArrayUtil;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.*;
import java.util.stream.Collector;
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() {
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> 此处是idparentId的泛型限制
* @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> 此处是idparentId的泛型限制
* @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> 此处是idparentId的泛型限制
* @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;
};
}
}

View File

@ -4,9 +4,7 @@ import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Spliterator;
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>
*
* @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).
* toTree(Student::getId, Student::getParentId, Student::setChildren);
* }</pre>
* @author VampireAchao
*/
public <R extends Comparable<R>> List<T> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter) {
// 使用 parentId == null 判断是否为根节点
final Predicate<T> parentPredicate = node -> null == pIdGetter.apply(node);
return toTree(idGetter, pIdGetter, childrenSetter, parentPredicate);
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter) {
return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, isParallel()));
}
/**
* 将集合转换为树自定义根节点的判断条件
* 将集合转换为树自定义根节点的判断条件,内置一个递归注意内存开销
* 因为需要在当前传入数据里查找所以这是一个结束操作
*
* @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).
* .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
* }</pre>
* @author VampireAchao
*/
public <R extends Comparable<R>> List<T> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter,
final Predicate<T> parentPredicate) {
Objects.requireNonNull(idGetter);
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());
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter,
final Predicate<T> parentPredicate) {
return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, parentPredicate, isParallel()));
}
/**

View File

@ -182,7 +182,7 @@ public class EasyStreamTest {
Assert.assertEquals(collect2, distinctBy2);
Assert.assertEquals(
4, EasyStream.of(1, 2, 2, null, 3, null).parallel(true).distinct(t -> Objects.isNull(t) ? null : t.toString()).sequential().count()
4, EasyStream.of(1, 2, 2, null, 3, null).parallel(true).distinct(t -> Objects.isNull(t) ? null : t.toString()).sequential().count()
);
}
@ -477,7 +477,7 @@ public class EasyStreamTest {
Student.builder().id(8L).name("jobob").parentId(5L).build()
)
// 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(
Student.builder().id(1L).name("dromara").matchParent(true)
.children(asList(Student.builder().id(3L).name("hutool").parentId(1L)
@ -547,10 +547,6 @@ public class EasyStreamTest {
public Student() {
// this is an accessible parameterless constructor.
}
public Boolean getMatchParent() {
return BooleanUtil.isTrue(matchParent);
}
}
@Test