Boh*_*ohn 10 c# tree-traversal
让我们把它想象成一个家谱,一个父亲有孩子,孩子有孩子,孩子有孩子等...所以我有一个递归函数,让父亲使用递归来获得孩子,现在只需打印他们调试输出窗口...但在某些时候(让它运行并打印26000行后一小时)它给了我一个StackOverFlowException.
那真的是内存不足吗?嗯?那我不应该得到"内存不足"吗? 在其他帖子上,我发现有人说如果递归调用的次数太多,你可能仍会得到一个SOF异常......
无论如何,我的第一个想法是将树打破成更小的子流...所以我知道我的根父亲总是有这五个孩子的事实,所以不是一次用root调用我的方法传递给它,我说好了有孩子的根传递给它五次.它帮助我思考..但它们中的一个仍然是如此之大 - 当它崩溃时26000行 - 并且仍然有这个问题.
如何在某个特定深度的运行时创建应用程序域并创建新进程? 这有帮助吗?
如何创建自己的堆栈并使用它而不是递归方法?这有帮助吗?
这里也是我的代码的高级别,请看一下,也许实际上有一些愚蠢的错误会导致SOF错误:
private void MyLoadMethod(string conceptCKI)
{
// make some script calls to DB, so that moTargetConceptList2 will have Concept-Relations for the current node.
// when this is zero, it means its a leaf.
int numberofKids = moTargetConceptList2.ConceptReltns.Count();
if (numberofKids == 0)
return;
for (int i = 1; i <= numberofKids; i++)
{
oUCMRConceptReltn = moTargetConceptList2.ConceptReltns.get_ItemByIndex(i, false);
//Get the concept linked to the relation concept
if (oUCMRConceptReltn.SourceCKI == sConceptCKI)
{
oConcept = moTargetConceptList2.ItemByKeyConceptCKI(oUCMRConceptReltn.TargetCKI, false);
}
else
{
oConcept = moTargetConceptList2.ItemByKeyConceptCKI(oUCMRConceptReltn.SourceCKI, false);
}
//builder.AppendLine("\t" + oConcept.PrimaryCTerm.SourceString);
Debug.WriteLine(oConcept.PrimaryCTerm.SourceString);
MyLoadMethod(oConcept.ConceptCKI);
}
}
Run Code Online (Sandbox Code Playgroud)
Mar*_*ers 10
如何创建自己的堆栈并使用它而不是递归方法?这有帮助吗?
是!
当你实例化它时,Stack<T>它会在堆上生存并且可以任意增大(直到你用完可寻址的内存).
如果使用递归,则使用调用堆栈.调用堆栈比堆小得多.默认值为每个线程1 MB的调用堆栈空间.请注意,这可以更改,但不建议这样做.
StackOverflowException 与 OutOfMemoryException 有很大不同。
OOME 意味着进程根本没有可用的内存。这可能是在尝试使用新堆栈创建新线程时,或者尝试在堆上创建新对象时(以及其他一些情况)。
SOE 意味着线程的堆栈 - 默认情况下为 1M,但可以在线程创建时进行不同的设置,或者如果可执行文件具有不同的默认值;因此 ASP.NET 线程默认为 256k,而不是 1M - 已耗尽。这可能是在调用方法或分配本地方法时发生的。
当你调用一个函数(方法或属性)时,调用的参数被放置在堆栈上,函数返回时应返回的地址被放置在堆栈上,然后执行跳转到被调用的函数。然后一些局部变量将被放入堆栈中。随着函数继续执行,可能会在其上放置更多内容。stackalloc还将显式地使用一些堆栈空间,否则将使用堆分配。
然后它调用另一个函数,同样的情况再次发生。然后该函数返回,执行跳转回存储的返回地址,堆栈中的指针向后移动(无需清理堆栈上的值,它们现在被忽略)并且该空间再次可用。
如果你用完那 1M 空间,你会得到一个StackOverflowException. 因为 1M(甚至 256k)对于这些用途来说是大量内存(我们不会在堆栈中放入真正大的对象),因此可能导致 SOE 的三件事是:
stackalloc,但事实并非如此,他们很快就用完了那 1M。您遇到的是第 4 种情况。第 1 种和第 2 种情况非常罕见(您需要非常谨慎地冒它们的风险)。情况 3 是迄今为止最常见的,它表明存在一个错误,即递归不应该是无限的,但错误意味着它是无限的。
具有讽刺意味的是,在这种情况下,您应该庆幸您采用了递归方法而不是迭代方法 - SOE 揭示了错误及其所在位置,而使用迭代方法时,您可能会遇到无限循环,导致一切停止,这可以更难找到。
现在对于情况 4,我们有两个选择。在非常罕见的情况下,我们的调用稍微过多,我们可以在具有更大堆栈的线程上运行它。这不适用于你。
相反,您需要从递归方法更改为迭代方法。大多数时候,这并不难,但可能会很麻烦。该方法没有再次调用自身,而是使用循环。例如,考虑阶乘方法的经典教学示例:
private static int Fac(int n)
{
return n <= 1 ? 1 : n * Fac(n - 1);
}
Run Code Online (Sandbox Code Playgroud)
我们不使用递归,而是使用相同的方法循环:
private static int Fac(int n)
{
int ret = 1;
for(int i = 1; i <= n, ++i)
ret *= i;
return ret;
}
Run Code Online (Sandbox Code Playgroud)
您可以看到为什么这里的堆栈空间较少。迭代版本在 99% 的情况下也会更快。现在,想象一下我们不小心调用了Fac(n)第一个,而忽略了++i第二个中的 - 每个中的等效错误,并且它导致第一个中出现 SOE 和第二个中永远不会停止的程序。
对于您正在谈论的那种代码,您根据以前的结果不断产生越来越多的结果,您可以将获得的结果放入数据结构中(Queue<T> 并且Stack<T>两者都可以很好地用于许多情况)所以代码变成这样):
private void MyLoadMethod(string firstConceptCKI)
{
Queue<string> pendingItems = new Queue<string>();
pendingItems.Enqueue(firstConceptCKI);
while(pendingItems.Count != 0)
{
string conceptCKI = pendingItems.Dequeue();
// make some script calls to DB, so that moTargetConceptList2 will have Concept-Relations for the current node.
// when this is zero, it means its a leaf.
int numberofKids = moTargetConceptList2.ConceptReltns.Count();
for (int i = 1; i <= numberofKids; i++)
{
oUCMRConceptReltn = moTargetConceptList2.ConceptReltns.get_ItemByIndex(i, false);
//Get the concept linked to the relation concept
if (oUCMRConceptReltn.SourceCKI == sConceptCKI)
{
oConcept = moTargetConceptList2.ItemByKeyConceptCKI(oUCMRConceptReltn.TargetCKI, false);
}
else
{
oConcept = moTargetConceptList2.ItemByKeyConceptCKI(oUCMRConceptReltn.SourceCKI, false);
}
//builder.AppendLine("\t" + oConcept.PrimaryCTerm.SourceString);
Debug.WriteLine(oConcept.PrimaryCTerm.SourceString);
pendingItems.Enque(oConcept.ConceptCKI);
}
}
}
Run Code Online (Sandbox Code Playgroud)
(我还没有完全检查这一点,只是添加了排队而不是递归到问题中的代码)。
这应该与您的代码或多或少相同,但是是迭代的。希望这意味着它会起作用。请注意,如果您检索的数据有循环,则此代码中可能存在无限循环。在这种情况下,当队列中填充了过多的内容而无法处理时,此代码将引发异常。您可以调试源数据,或使用 aHashSet来避免将已处理的项目排队。
编辑:更好地添加如何使用 HashSet 来捕获重复项。首先设置一个 HashSet,这可能只是:
HashSet<string> seen = new HashSet<string>();
Run Code Online (Sandbox Code Playgroud)
或者,如果使用的字符串不区分大小写,则最好使用:
HashSet<string> seen = new HashSet<string>(StringComparison.InvariantCultureIgnoreCase) // or StringComparison.CurrentCultureIgnoreCase if that's closer to how the string is used in the rest of the code.
Run Code Online (Sandbox Code Playgroud)
然后,在您使用该字符串之前(或者可能在您将其添加到队列之前),您需要执行以下操作之一:
如果重复的字符串不应该发生:
if(!seen.Add(conceptCKI))
throw new InvalidOperationException("Attempt to use \" + conceptCKI + "\" which was already seen.");
Run Code Online (Sandbox Code Playgroud)
或者,如果重复的字符串有效,并且我们只想跳过执行第二次调用:
if(!seen.Add(conceptCKI))
continue;//skip rest of loop, and move on to the next one.
Run Code Online (Sandbox Code Playgroud)