【问题标题】:Memory Management Recursion Java内存管理递归 Java
【发布时间】:2014-05-30 10:42:38
【问题描述】:

我正在使用大型 ArrayList 在 Java 中进行递归。在一个递归步骤中,我将此列表拆分为两个列表,每个列表大小为一半,并将相同的方法递归地应用于两个列表。但是,由于拆分后不再需要大列表,因此我想将其从内存中删除。在这里搜索了一段时间后,我想出了这个:

public some_object recursiveMethod(ArrayList large_List) {
    //Compute the two sublists
    ArrayList lower_half = lowerHalf(large_List);
    ArrayList upper_half = upperHalf(large_List);

   //Delete large list from memory
   large_List = null;

   //Recursively call on smaller lists
   return procedure(recursiveMethod(lower_half),recursiveMethod(upper_half));
}

这会起作用吗,即 GC 会删除大列表吗?

【问题讨论】:

  • 如果还有其他对您的 large_list 的引用,这可能还不够。

标签: java memory memory-management recursion


【解决方案1】:

它不起作用,因为lower_halfupper_class 仍在引用调用者中的列表,直到它完成才取消引用它们。

例如取procedure的第一个实参:recursiveMethod(lower_half)lower_half reference 被复制到被调用的recursiveMethod large_List 实际参数中。所以你有相同的列表:

  • 在调用方引用为lower_half
  • 在当前调用范围内为large_list

在您当前的通话中,您会将引用 large_list 设为空,但不会将调用者的 lower_half 设为空。因此,该列表仍然在某处被引用,因此 GC 不会收集该列表。

【讨论】:

  • 感谢您的快速回复。有没有办法实现我的目标,或者这在 Java 中根本不可能?
  • 对不起,我跟不上。您是否建议我在班级中将属性 lower_half 和 upper_half 设为全局?那么递归将如何工作?你能举一个简短的例子吗?谢谢大家!
  • 谢谢,现在我明白了。虽然,据我了解,recursiveMethod 的调用不应该放在现在全局变量的相应设置的正下方。照你的说法,当机器调用“recursiveMethod(upper_half)”时,upper_half变量已经被“recursiveMethod(lower_half)”的调用覆盖了,不是吗?
  • @Skrodde 是的,我删除了那个解决方案建议。
【解决方案2】:

看起来像divide & conquer。在这种情况下,最好通过使用List.subList()(包装器,引用大后备列表)或一对普通的索引。

如果这些子列表必须独立于原始列表进行修改,则可以在进入递归处理之前复制该大列表一次,这仍然应该比多个独立的子列表副本更有效。

public some_object recursiveMethod(List large_List) {
    //Create views of the two sublists
    List lower_half = large_List.subList(0,large_List.size()/2);
    List upper_half = large_List.subList(large_List.size()/2,large_List.size());

   //Recursively call on list views
   return procedure(recursiveMethod(lower_half),recursiveMethod(upper_half));
}

public some_object recursiveMethod2(ArrayList large_List, int begin, int end) {
    //Create views of the two sublists
    int size = end - begin;
    int lowerBegin = begin;
    int lowerEnd = begin + size/2; //exclusive
    int upperBegin = lowerEnd;
    int upperEnd = begin + size; //exclusive

   //Recursively call on list views
   return procedure(recursiveMethod2(large_List,lowerBegin,lowerEnd),recursiveMethod2(large_List,upperBegin,upperEnd));
}

【讨论】:

  • 遗憾的是,我不能使用 List 结构,但需要 ArrayList 结构,因为我需要快速找到两个子列表中的中位数。从一个 ArrayList 我可以做到这一点,但不能从一个列表。
  • @Skrodde:您可以传递large_List 以及成对的开始和结束索引。为了清楚起见,您可以添加 recursiveMethod() 的整个实现。 - 见编辑
  • 想了想,你的方法似乎最适合我。谢谢大家!
【解决方案3】:

为什么要放弃旧数组并创建两个较小的数组?也许可以保留数组并只进行“虚拟”划分?

lowerHalf 将返回一个对象,该对象记录数组中的开始和结束索引作为新的虚拟数组。

这样你就可以分割数组而不用乱动内容...

public some_object recursiveMethod(ArrayList large_List, int start, int end) {
    //Compute the two sublists
    StartEndIndexObj lower_half = lowerHalf(large_List);
    StartEndIndexObj upper_half = upperHalf(large_List);

   //Recursively call on smaller lists
   return procedure(recursiveMethod(large_List, lower_half.start, lower_half.end),
                    recursiveMethod(large_List, upper_half.start, upper_half.end));
    }

另请参阅 Sam 对 List.subList 版本的回答。

【讨论】:

  • 顺便说一句...奇怪的是我有时会转向“手动”的做事方式,因为在 pascal & c 时代我们没有其他方法,而不是使用库解决方案?我老了……
  • 可悲的是,“lowerHalf”和“upperHalf”比这更复杂。相应的值任意分散在“large_List”中。按照您的方法,我确实需要重新排列(在最坏的情况下) large_List 中的所有元素。
  • 也许你可以使用不同的数据结构,比如平衡树?您可以“重用”内容对象,还可以直接访问“中间”...您甚至可以在构建树时保留一个大小的计数...
  • 关于自平衡树的维基百科条目:en.wikipedia.org/wiki/Self-balancing_binary_search_tree
【解决方案4】:

GC 将删除所有没有引用的对象。但是,这不适用于所有 JVM。如果你有一个带有 GC 的 JVM,我不是 100% 支持的。

【讨论】:

  • 但是large_List在之前的递归中被upper_halfLower_half引用,所以在之前的递归方法完成之前无法回收。
  • 有没有没有GC的JVM?
猜你喜欢
  • 2016-03-20
  • 2012-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-07
  • 1970-01-01
  • 2021-04-28
  • 1970-01-01
相关资源
最近更新 更多