【发布时间】:2016-09-11 09:51:20
【问题描述】:
更新:请注意,就目前而言,这个问题没有多大意义,因为调用堆栈将独立于类型 T 增长。调用堆栈仅取决于正在处理的列表中的节点数- 这些节点中包含的数据对调用堆栈的大小没有影响。
假设我有一个“remove-node-from-list”方法的递归 java 实现,看起来像这样:
// head
public void remove(T data) {
if (data == null)
throw new IllegalArgumentException();
head = remove(head, data);
}
// other nodes of list
private ListNode remove(ListNode current, T data) {
if (current == null)
return null;
if (current.data.compareTo(data) == 0)
return current.next;
current.next = remove(current.next, data);
return current;
}
让我们进一步假设T 的类型是int
我的问题是: 与我正在处理的列表的大小相比,调用堆栈(在最坏的情况下)会有多大?
到目前为止我的想法: 从我所见,Java 8 并没有优化递归(对吗?我的整个分析都假设这是真的)。因此,我们将拥有一个随列表大小线性增长的调用堆栈。
更准确地说:这个方法remove(ListNode current, T data) 基本上会在我的列表中的每个节点的调用堆栈上放置一个节点引用(32 位在 32 位拱上)和一个 int(也是 32 位)。
现在,由于列表中的一个节点还包含一个引用和一个int,因此一个节点的大小实际上与这些递归调用之一几乎相同。实际上,其中一个调用甚至可能使用更多空间(返回地址也必须被压入堆栈,对吗?或者可以以某种方式优化它吗?)。
所以如果我是正确的,那么在最坏的情况下调用堆栈基本上会和列表本身一样大(当要删除的元素是列表中的最后一个时)!真的是这样吗?如果我是正确的,那么这将意味着处理ints 列表所需的调用堆栈的一个因子甚至更大!因此,在处理大型列表(实际上甚至没有那么大)时,如果使用默认设置的 stacksize (0.5M) 运行它,比如说 1M,就会得到一个 stackoverflow
我知道对于更大的数据类型,这个因素会更少,但无论如何我仍然坚持的基本上是这样的:如果没有进行优化,那么调用堆栈将随着列表的大小线性增长 - 因此人们应该更喜欢迭代而不是递归过程。
总的来说,我会说,调用堆栈将大致线性增长,系数(size_of_all_arguments_in_call + size_of_return_address)/size_of_a_single_listelement 在ints 列表的情况下几乎相同,对吧?请不要误会size_of_all_arguments 的意思:我知道我们只将引用的值传递给那些调用中的对象,而不是整个对象本身。因此,对于大型物体,这个因素可能不是那么引人注目,但我仍然会说人们不应该接受这种不必要的线性空间开销。但是,对于ints 的列表,该因子约为。 1.
这个分析是正确的还是我遗漏了什么?
背景:与我讨论这个问题的人试图让我相信 java 中的调用堆栈不会如此显着增长,并指出我遗漏了一些明显的东西(很遗憾没有告诉我什么对我来说,这真的一点都不明显)-因为我不知道我错过了什么,所以我很想学习;)
相关:
对我来说,相关问题中缺少的主要内容是调用堆栈实际使用多少空间
【问题讨论】:
-
我不确定 JVM 是否有任何原因,但您可以看到这篇文章和链接的重复项 stackoverflow.com/questions/17250883/…
-
@Lee 还要考虑帧大小会使因素更糟,对吗?帧大小几乎就是我所说的“将返回地址推入堆栈”。但是,是的,在严谨的分析中也应该考虑到这一点。
-
@cricket_007 不幸的是,我在您链接的帖子中没有看到任何回答我的问题的内容 - 对我来说,这与增加堆栈的大小或垃圾收集无关。
标签: java list recursion linked-list stack-overflow