2015-09-19 73 views
0

Java。足夠堆棧溢出?

10 000遞歸無效函數調用引用和兩個整數作爲參數後,我得到堆棧溢出錯誤是正常的嗎?

得到6GB RAM內存,嘗試通過IDE和命令行運行。我很確定代碼是正確的,遞歸應該完成。

這是關於瓷磚地圖編輯器的填充工具。它開始於某個瓷磚上,如果同時出現的瓷磚屬於同一類型並且不會再回來,則會上下左右移動。

嘗試不同的方法,這裏是一個額外的布爾表指示是否[X]的訪問[Y]瓷磚和遞歸完成後更換標磚:

public void fillRec(Tile t, int column, int row) { 
    if (affected[column][row] || t.getName() != pattern) 
     return; 

    /*t.replaceMe(editor.currentTileButton.spawnTile(column, row, 
      editor.tileMap));*/ 
    affected[column][row] = true; 

    if (column < editor.tileMap.tilesX - 1) { 
     fillRec(editor.tileMap.tiles[column + 1][row], column + 1, row); 
    } 
    if (column > 0) { 
     fillRec(editor.tileMap.tiles[column - 1][row], column - 1, row); 
    } 
    if (row < editor.tileMap.tilesY - 1) { 
     fillRec(editor.tileMap.tiles[column][row + 1], column, row + 1); 
    } 
    if (row > 0) { 
     fillRec(editor.tileMap.tiles[column][row - 1], column, row - 1); 
    } 
} 

這工作得很好用〜75x75地圖,功能也取代了瓷磚,並在他們的身體中做了其他重要的東西。

+1

''我很確定代碼是正確的,並且遞歸應該完成。「 - 但沒有相關的代碼,我們不能確定任何事情。如果我是你的鞋子,如果我的代碼沒有按預期行事,我也不會對正確性做任何假設。請考慮發佈一個體面的[mcve]讓我們測試和審查。 –

+0

你的程序的目標是什麼? – developer

+0

發佈您的代碼。一般來說,遞歸需要足夠的「堆棧」大小。 –

回答

0

這取決於這些功能有多少數據將在相對於配置(或默認)堆棧大小的堆棧,因此它不僅是所使用的參數傳遞給函數調用堆棧大小。

所以是的,這聽起來並不正常。你應該玩堆棧大小或以不同的方式實施。

3

是的,每個方法調用使用了一個堆棧幀。如果你想在Java中使用大規模遞歸,你需要使用一個蹦牀 - 它可以爲堆空間交換堆棧空間。蹦牀通常有兩種狀態

  1. 完成
  2. 更多的工作要做

完成狀態保存最終的結果,以及更多的工作要做可以與供應商來實現(在Java中8 )或類似的構造,這使得下一個遞歸調用。蹦牀實現應該管理對你的方法的調用,並迭代而不是遞歸。

下面是一個簡單的蹦牀循環示例。

Trampoline<Integer> loop(int times,int sum){ 

    if(times==0) 
     return Trampoline.done(sum); 
    else 
     return Trampoline.more(()->loop(times-1,sum+times)); 
} 

要撥打電話,以循環

loop(100,10).result(); 

注意方法立即返回一個懶惰的蹦牀對象(即不進行相加),以及蹦牀貫穿簡單相加的算法,當結果被稱爲 - 以迭代方式,而不是遞歸方式。

在我編寫的名爲cyclops-trampoline的庫中有一個可以使用的蹦牀實現。或者,如果你喜歡這裏是如何推出你自己的(這個實現使用了Mario Fusco在Java 8 Stream中管理蹦牀迭代的一個很好的技術)。

public interface Trampoline<T> { 


default Trampoline<T> bounce(){ 
    return this; 
} 


T result(); 


default boolean complete() { 
    return true; 
} 



public static <T> Trampoline<T> done(T result) { 
    return() -> result; 
} 


public static <T> Trampoline<T> more(Trampoline<Trampoline<T>> trampoline) { 
    return new Trampoline<T>() { 

     @Override 
     public boolean complete() { 
      return false; 
     } 

     @Override 
     public Trampoline<T> bounce() { 
      return trampoline.result(); 
     } 

     public T result() { 
      return trampoline(this); 
     } 

     T trampoline(Trampoline<T> trampoline) { 

      return Stream.iterate(trampoline,Trampoline::bounce) 
         .filter(Trampoline::complete) 
         .findFirst() 
         .get() 
         .result(); 


     } 
     }; 
    } 
} 
0

這聽起來很正常。如果不另外指定,則默認Java堆棧大小爲1Mb或更少,具體取決於您的JVM和執行平臺。以〜10,000遞歸調用的堆棧溢出聽起來對於具有默認大小的堆棧聽起來相當合理。

  • 您可以使用-Xss選項更改JVM的默認堆棧大小;例如-Xss10m將默認大小設置爲10MB。

  • 您還可以直接通過Thread構造函數指定線程堆棧大小。


然而,這說明了一點,是不是很明顯新的Java程序員。與典型的函數式編程語言(以及許多其他語言)不同,標準的Java實現不會執行「尾部調用優化」。這意味着遞歸調用序列總是需要與最大遞歸深度成比例的堆棧空間。

對於喜歡使用遞歸而非迭代的程序員來說,這是一個潛在的問題。不幸的是,如果你的數據是這樣的深度遞歸是可能的,你真的需要轉換爲迭代解決方案。 (或者找到一些其他方式將「遞歸狀態」從堆棧中移走)。