说flatMap 流并不懒惰,这有点错误。它有点懒惰,虽然它的懒惰确实是有限的。让我们使用一些自定义的Collection 来跟踪您的Tree 类中的请求元素:
private final Set<String> requested = new LinkedHashSet<>();
private class MyList extends AbstractList<String> implements RandomAccess
{
private final String[] data;
public MyList(String... data) {
this.data = data;
}
@Override
public String get(int index) {
requested.add(data[index]);
return data[index];
}
@Override
public int size() {
return data.length;
}
}
现在让我们用一些树数据预初始化你的类:
public Tree() {
// "1" is the root note, contains three immediate descendants
edges.put("1", new MyList("2", "3", "4"));
edges.put("2", new MyList("5", "6", "7"));
edges.put("3", new MyList("8", "9", "10"));
edges.put("8", new MyList("11", "12"));
edges.put("5", new MyList("13", "14", "15"));
edges.put("7", new MyList("16", "17", "18"));
edges.put("6", new MyList("19", "20"));
}
最后,让我们检查一下在不同的限制值上实际从列表中请求了多少元素:
public static void main(String[] args) {
for(int i=1; i<=20; i++) {
Tree tree = new Tree();
tree.descendants("1").limit(i).toArray();
System.out.println("Limit = " + i + "; requested = (" + tree.requested.size()
+ ") " + tree.requested);
}
}
输出如下:
Limit = 1; requested = (0) []
Limit = 2; requested = (12) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18]
Limit = 3; requested = (12) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18]
Limit = 4; requested = (12) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18]
Limit = 5; requested = (12) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18]
Limit = 6; requested = (12) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18]
Limit = 7; requested = (12) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18]
Limit = 8; requested = (12) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18]
Limit = 9; requested = (12) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18]
Limit = 10; requested = (12) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18]
Limit = 11; requested = (12) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18]
Limit = 12; requested = (12) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18]
Limit = 13; requested = (12) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18]
Limit = 14; requested = (18) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18, 3, 8, 11, 12, 9, 10]
Limit = 15; requested = (18) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18, 3, 8, 11, 12, 9, 10]
Limit = 16; requested = (18) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18, 3, 8, 11, 12, 9, 10]
Limit = 17; requested = (18) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18, 3, 8, 11, 12, 9, 10]
Limit = 18; requested = (18) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18, 3, 8, 11, 12, 9, 10]
Limit = 19; requested = (18) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18, 3, 8, 11, 12, 9, 10]
Limit = 20; requested = (19) [2, 5, 13, 14, 15, 6, 19, 20, 7, 16, 17, 18, 3, 8, 11, 12, 9, 10, 4]
因此,当仅请求根音符时,不会执行对子项的访问(因为Stream.concat 很聪明)。当请求第一个直接子树时,即使没有必要,也会处理该子树的整个子树。然而,直到第一个完成后,才会处理第二个直接子级。这对于短路情况可能会有问题,但在大多数情况下,您的终端操作不会短路,因此它仍然是一种不错的方法。
至于您对内存消耗的担忧:是的,它根据树的深度吃掉内存(更重要的是它吃掉了堆栈)。如果您的树有数千个嵌套级别,您的解决方案将遇到问题,因为您可能会点击 StackOverflowError 以获取默认的 -Xss 设置。对于数百个深度级别,它可以正常工作。
我们在应用程序的业务逻辑层中使用了类似的方法,它对我们来说效果很好,尽管我们的树很少超过 10 层。