2016-05-15 89 views
3

更新:请注意,现在这个问题没有多大意义,因为callstack将独立于类型T而增长。callstack完全取决于正在处理的列表中的节点数 - 这些数据包含在节点对调用堆栈的大小没有影响。递归使用多少堆栈(从列表中删除节点)?


比方说,我有一个递归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; 
} 

让我们进一步假设该类型Tint

我的问题是:与我正在处理的列表大小相比,调用堆栈会得到多少(在最坏的情况下)?

什么我至今认为: 从我所看到的Java 8不优化递归(?右我的整个分析假设这是真的)。因此,我们将有一个与列表大小成线性增长的调用堆栈。

更确切地说:这个方法remove(ListNode current, T data)将基本上在我的列表中的每个节点的调用堆栈上放置一个节点引用(32位位置上的32位)和一个int(也是32位)。

现在,由于列表中的一个节点也由一个引用和一个节点构成,因此节点实际上与其中一个递归调用的大小几乎相同。实际上,其中一个调用甚至可能会占用更多空间(返回地址也必须被推入堆栈,对吗?或者可以通过某种方式进行优化?)。

所以,如果我是正确的,那么在最坏情况下(当要移除的元素是列表中的最后一个时),callstack基本上会像列表本身一样大!这是真的吗?如果我是正确的,那么就处理int列表所需的堆栈而言,这意味着1或甚至更大的因子!因此,当处理大型列表时(实际上并不那么大),比如说1M人将获得一个stackoverflow,如果用stacksize(0.5M)的默认设置运行的话,我会得到一个stackoverflow。我知道这样一个事实,对于更大的数据类型来说,这个因子会更少,但是我仍然坚信在任何情况下基本上都是这样的:如果没有进行优化,那么callstack将随着列表大小线性增长 - 因此一个应该更喜欢递归过程的迭代。

一般来说,我会说,这个callstack将会以一个因子(size_of_all_arguments_in_call + size_of_return_address)/size_of_a_single_listelement粗略线性增长,在int列表的情况下,它几乎是一样的,对吧?请不要误解我的size_of_all_arguments:我知道我们只传递对这些调用中对象的引用的值,而不是整个对象本身。因此,对于大型物体来说,这个因素可能不会那么戏剧化,但我仍然认为不应该接受不必要的线性空间开销。但是,对于int的列表,该因子将是约。 1.

这个分析是否正确,或者我错过了什么?

背景:我与讨论这样做的人,试图说服我,在Java调用堆栈不会那么显着增长,并指出我缺少明显的东西(很遗憾没能告诉我什么,对我来说真的不是很明显的话) - 因为我不知道它是什么,我很想念我渴望学习;)

相关:

在我相关的问题,缺少的主要事情是多少空间将调用堆栈实际使用从

+0

我不知道如果JVM帐户的东西,但你可以看到这篇文章和链接的重复http://stackoverflow.com/questions/17250883/is-it-possible-to-determine-how-much -space-is-on-the-stack –

+0

找到了Java问题http://stackoverflow.com/questions/20030120/java-default-stack-size –

+0

我认为你还需要考虑这个框架的大小: http://www.herongyang.com/JVM/Stack-Overflow-and-JVM-Stack-Frame.html – Lee

回答

0

的堆栈如何做一个递归使用(删除节点列表)?

这是一个难以回答的问题,无需分析代码或在特定的JVM上具体细节。这是因为Java Virtual Machine Specification (JVMS)留下了许多细节的实现者。

JVMS Chapter 2:实现细节不属于 Java虚拟机的规范的一部分会不必要地限制 创造力实现者。例如,运行时间 数据区的内存布局,垃圾收集算法中使用,并在Java虚拟机指令的任何内部 优化(例如, 其翻译成机器代码)都留给的自由裁量权 实现者。的那些细节

部分是堆栈和堆栈帧实现。而且因为它似乎你实际上问由递归删除和列表中的算法处理创建堆栈之间的关系,我们要考虑的是,堆栈帧甚至可能不是大堆栈上,你认为(或在你的问题中提出)。

JVMS (§2.5.2):因为Java虚拟机堆是从未 直接操作除了push和pop帧,帧可能是 堆分配。

这意味着每个堆栈帧可能只有一个引用放入堆栈。如果是这样,这将使栈和它的处理为您提供真正例子微不足道的列表之间的关系。让我们看一下一些数字:

根据Algorithms (Sedgewick, Wayne)

  • Integer为24个字节
    开销+实例变量(int)+填充
    = 16 + 4 + 4 = 24个字节

  • 一个非静态递归定义的Node类嵌套在关联的 List是40个字节 开销+引用+额外的开销(参照List类)
    = 16 + 8 + 8 + 8 = 40字节

因此,我们可以看到有每个条目为64字节列表。所以,如果有一个框架堆分配的实现,堆栈和你正在处理的列表之间的关系将是8/64 = 1/8 bytes

当在堆栈上分配堆栈帧时,确定堆栈的大小更加困难。事实上,我们需要进一步的实施细节来确定它。

JVMS (§2.6. Frames): 局部变量阵列和操作数堆栈的尺寸被确定 在编译时和与代码沿着 被提供与所述框架(§4.7.3)相关联的方法。因此,帧数据结构的大小仅取决于Java虚拟机的实现,并且这些结构的存储器可以在方法调用时同时分配为 。

即便如此,我想说,就你的例子而言,堆栈相对于列表(在此讨论内存总大小)与1:1不可能成比例或接近1:1。当然,你可以提出一个具体的问题来验证你的论点,但这可能是微不足道的,可能不是你提供的例子。