所以,我有一个基本的树结构,由树节点链接到其他树节点,父节点和子节点引用。我想创建一个返回Stream的方法,该流从叶节点流到根节点或从根流到叶。我已经实现了这个,但我正在寻找一个创建最少量对象的解决方案。优选没有。这是我的代码:
public class TreeNode<TNode> {
private TNode iValue;
private TreeNode<TNode> iParentNode = null;
private List<TreeNode<TNode>> iChildren = new ArrayList<>();
public TreeNode(TNode value) {
this(value, null);
}
private TreeNode(TNode value, TreeNode<TNode> parentNode) {
iValue = value;
iParentNode = parentNode;
}
public Stream<TreeNode<TNode>> streamFromLeaf() {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new LeafFirstIterator(this), Spliterator.SIZED),
false);
}
public Stream<TreeNode<TNode>> streamFromRoot() {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new RootFirstIterator(this), Spliterator.SIZED),
false);
}
public TNode getValue() {
return iValue;
}
public TreeNode<TNode> getParent() {
return iParentNode;
}
public TreeNode<TNode> addChild(TNode childValue) {
TreeNode<TNode> childNode = new TreeNode<TNode>(childValue, iNodeNameFunction, this);
iChildren.add(childNode);
return childNode;
}
public boolean isLeaf() {
return iChildren.size() == 0;
}
public boolean isRoot() {
return iParentNode == null;
}
public List<TreeNode<TNode>> getChildren() {
return iChildren;
}
class LeafFirstIterator implements Iterator<TreeNode<TNode>> {
private TreeNode<TNode> iNextNode;
LeafFirstIterator(TreeNode<TNode> leafNode) {
iNextNode = leafNode;
}
@Override
public boolean hasNext() {
return iNextNode != null;
}
@Override
public TreeNode<TNode> next() {
TreeNode<TNode> current = iNextNode;
iNextNode = current.getParent();
return current;
}
}
class RootFirstIterator implements Iterator<TreeNode<TNode>> {
private List<TreeNode<TNode>> iNodes = new ArrayList<>();
private int iNextIndex;
RootFirstIterator(TreeNode<TNode> leafNode) {
TreeNode<TNode> currentNode = leafNode;
while (currentNode != null) {
iNodes.add(currentNode);
currentNode = currentNode.getParent();
}
iNextIndex = iNodes.size() - 1;
}
@Override
public boolean hasNext() {
return iNextIndex >= 0;
}
@Override
public TreeNode<TNode> next() {
return iNodes.get(iNextIndex--);
}
}
}
这很好但我的“问题”是,为每个流调用创建了很多对象。
流将被大量使用,所以我想尽可能避免创建对象。在没有流的情况下迭代树结构是一件简单的事情。现在我只使用流的map方法。我可以有一个方法,它接受一个Consumer,迭代深度并为每个节点调用消费者,并且不会创建任何对象,但是我会丢失所有流功能。这看起来像这样:
public void iterateUp(Consumer<TreeNode<TNode>> consumer) {
doIterateUp(this, consumer);
}
public static <T> void doIterateUp(TreeNode<T> node, Consumer<TreeNode<T>> consumer) {
if (node == null)
return;
consumer.accept(node);
doIterateUp(node.getParent(), consumer);
}
从根向下迭代就是这么简单。
有关于此的任何想法吗?我是以错误的方式来做这件事的吗? TreeNode应该实现还是扩展一些接口/类?如果有什么不清楚,请告诉我。
谢谢!
答案 0 :(得分:1)
不要使用流。使用您的替代方案,其名称为visitor pattern。
Streams并不总是正确的方法,这是使用访问者模式的经典案例。
如果您必须拥有一个流,请让您的消费者收集List中的节点然后对其进行流式传输,或者将消费者连接到Iterator的next()
方法(使用一些代码使其行为正常)并使用StreamSupport将其转换为流。
答案 1 :(得分:1)
我不会分享您的性能问题,但是,您的代码可以简化,这可能会解决您的一些担忧,因为这会产生副作用。
你犯了一个常见错误,就是从Iterator
实现开始,很可能是因为Iterator
接口很老而且众所周知。但实现它很麻烦,如果你直接实现Iterator
,你不需要在Spliterator
中包装Spliterator
,这只是一个很好的副作用:
public Stream<TreeNode<TNode>> streamFromLeaf() {
return StreamSupport.stream(new LeafFirstSpliterator<>(this), false);
}
static class LeafFirstSpliterator<TNode>
extends Spliterators.AbstractSpliterator<TreeNode<TNode>> {
private TreeNode<TNode> iNextNode;
LeafFirstSpliterator(TreeNode<TNode> leafNode) {
super(100, ORDERED|NONNULL);
iNextNode = leafNode;
}
public boolean tryAdvance(Consumer<? super TreeNode<TNode>> action) {
if(iNextNode==null) return false;
action.accept(iNextNode);
iNextNode=iNextNode.getParent();
return true;
}
}
对我来说,Spliterator
实现看起来更清晰,至少它没什么可担心的,它可能允许更快的流遍历。您可以考虑覆盖forEachRemaining
方法以及直接实现。
对于从root到leaf的流,临时存储似乎是不可避免的,但如果是,不要浪费时间进行低级编码,只需使用存储的内置流功能:
public Stream<TreeNode<TNode>> streamFromRoot() {
ArrayDeque<TreeNode<TNode>> deque = new ArrayDeque<>();
for(TreeNode<TNode> n = this; n != null; n = n.getParent())
deque.addFirst(n);
return deque.stream();
}