在递归函数中处理大数组时堆栈溢出

Posted

技术标签:

【中文标题】在递归函数中处理大数组时堆栈溢出【英文标题】:Stack overflow while processing large array in recursive function 【发布时间】:2015-09-23 20:19:00 【问题描述】:

如果数组列表太大,为什么下面的递归代码会导致堆栈溢出?我怎样才能解决这个问题并仍然保留递归模式?

var list = readHugeList();

var nextListItem = function() 
    var item = list.pop();

    if (item) 
        // process the list item...
        nextListItem();
    
;

【问题讨论】:

javascript 的调用堆栈大小非常有限。我相信当为 ES6 更新实现时这应该会改变,因为正确的尾调用是规范 IIRC 的一部分。要修复它,您需要以异步批处理的方式进行,但这会使您的代码需要回调。 @squint 另外,某些浏览器上的最大调用堆栈略高于 1400。Opera 12.17 及以下版本就是这种情况。一个解决方案是使用 1 毫秒的 setTimeout 您可以在这里找到一些浏览器堆栈大小:***.com/questions/7826992/… @IsmaelMiguel 我需要递归解决方案,正如您之前提到的,使用 setTimeout 有效,所以我接受了答案。 好吧。请考虑这次。我会记住这些事情的。我是 *** 的新手,所以我正在学习它的流程 【参考方案1】:

这听起来很奇怪,但请使用setTimeout

像这样:

//fill it with 50000 elements
var list = Array(50001).join('1.1').split('.');

var nextListItem = function() 
    var item = list.pop();

    if (item)  //should be list.length

        // recursion here!
        setTimeout( nextListItem, 0 );

    
;
nextListItem();

现在递归无限

请注意,有些浏览器不喜欢那里的 0。 作为副作用,您的代码不会阻止浏览器。

【讨论】:

所有浏览器都会在0 或任何虚假值上失败。由于您正在更改列表,因此您的基本情况应该是检查list.length。此外,批量解决方案比为每个项目创建一个新的setTimeout 效率要高得多。它实际上从来都不是0 计时器,所以这可能需要相当长的时间。还有其他并发症,这取决于他实际在做什么。 @squint 你是对的。我只是展示了他可以做些什么来实现无限递归。是的,这需要相当长的时间(大约 50 秒),但代码是以非阻塞方式执行的,所以“有点”没问题。 我看到source article 也推荐使用setTimeout 作为堆栈溢出问题的解决方案。 不要使用setTimeout 来解决这个问题 – 有关更多详细信息,请阅读我对该主题的检查:***.com/a/43596323/633183 @naomik 很抱歉,您的考试没有说服我。此外,您的代码正在做上帝知道的事情,而这正在做一些更简单的事情。另外,您没有提到它如此慢的原因是浏览器等待至少 4 毫秒(以前是 10 毫秒),即使超时为 0 毫秒。事实上,我什至说它很慢(在上面的评论中)。虽然您的检查本身是有效的,但这并不重要:重要的是无限递归。为此,此答案以非阻塞方式工作得很好。 @naomik 然后随意添加您的答案,记住浏览器对 Promises、ES6 和所有爵士乐的支持状态。这是 2 年前提出的,从那时起发生了很多事情。事实上,没有指定环境。这意味着代码可以在 IE 5.5、Google Chrome 1.0、Netscape 或其他系统上运行。【参考方案2】:

似乎您只是在循环遍历一个数组。您是否尝试过使用简单的for 循环?

var list = readHugeList();
for (var i = 0; i < list.length; i++) 
    //Do something with list[i]

【讨论】:

这不会按照问题的要求保留递归。我确信他知道for 循环是如何工作的。 @squint 我同意你会期望几乎所有的前端 Web 开发人员都知道什么是简单的 for 循环,但是他的个人资料显示 ruby​​-on-rails 开发人员。不仅如此,我还遇到过很多开发人员经常尝试用复杂的代码来解决简单的问题,因为这正是他们大脑的工作方式。 可能是。然而,他特别想保留递归(这表明他知道命令式解决方案)。不说for 循环是不明智的。只是说它真的不能回答这个问题。坦率地说,有些问题用递归更容易解决。 @squint 好吧,希望 OP 会在此处回复 something 以提供任何一种指示。如果他们说“不,我不能这样做,原因是:X”,那么我将删除这个答案。 是的,我讨厌讨论时,除了提出问题的人之外的所有人都在场。【参考方案3】:
var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);

// requestAnimationFrame will get executed in each 16ms of duration.
requestAnimationFrame(processTaskList);

function processTaskList(taskStartTime) 
    var taskFinishTime;

    do 
        // Assume the next task is pushed onto a stack.
        var nextTask = taskList.pop();

        // Process nextTask.
        processTask(nextTask);

        // Go again if there’s enough time to do the next task.
        taskFinishTime = window.performance.now();
        while (taskFinishTime - taskStartTime < 3);
        if (taskList.length > 0)
            requestAnimationFrame(processTaskList);


【讨论】:

以上是关于在递归函数中处理大数组时堆栈溢出的主要内容,如果未能解决你的问题,请参考以下文章

函数 堆栈溢出

如何解决栈溢出

堆栈溢出一般是由啥原因导致的?

递归代码在数组列表偏大的情况下会导致堆栈溢出。一个解决办法

递归函数导致堆栈溢出

无限递归函数->堆栈溢出错误