C# 队列 - 内存不足
C# Queue - out of memory
我正在尝试制作一个扫雷游戏,我在其中制作了使用队列集合迭代淹没未知图块的方法
private void FloodIterative(Tile tile)
{
Queue<Tile> queue = new Queue<Tile>();
queue.Enqueue(tile);
while (queue.Count != 0)
{
Tile b = queue.Dequeue();
if (b.isDiscovered) continue;
if (b.type == Tile.Type.Mine || b.type == Bunka.Type.Invalid) continue;
b.isDiscovered = true;
state[b.position.x, b.position.y] = b;
if (b.type == Bunka.Type.Empty)
{
queue.Enqueue(GetBunka(b.position.x - 1, b.position.y));
queue.Enqueue(GetBunka(b.position.x + 1, b.position.y));
queue.Enqueue(GetBunka(b.position.x, b.position.y - 1));
queue.Enqueue(GetBunka(b.position.x, b.position.y + 1));
queue.Enqueue(GetBunka(b.position.x - 1, b.position.y - 1));
queue.Enqueue(GetBunka(b.position.x + 1, b.position.y + 1));
queue.Enqueue(GetBunka(b.position.x + 1, b.position.y - 1));
queue.Enqueue(GetBunka(b.position.x - 1, b.position.y + 1));
}
}
}
此方法效果很好,但仅适用于数量有限的未探索图块。如果图块编号太大,我的统一引擎输出:
OutOfMemoryException: 内存不足
System.Collections.Generic.Queue 1[T].SetCapacity(System.Int32 容量)(位于 <5a2009c85b134970925993880e2ecb2e>:0)
System.Collections.Generic.Queue 1[T].Enqueue (T item) (at <5a2009c85b134970925993880e2ecb2e>:0)
有什么解决办法吗?我可能需要手动释放内存吗?
非常感谢您。
您似乎为每个取出的物品排队了 8 件物品,并且您排队了重复的物品。在将项目放入队列之前检查它是否已经存在。
这是我不久前制作的扫雷游戏的片段。
了解给定方块周围有多少地雷会有所帮助。这可以在网格的初始构造期间设置。
private void RevealEmptyAdjacentTiles(Point point)
{
// Create a list to store the next tiles we need to check
//
var nextTilesToCheck = new List<Tile>();
// Loop through the adjacent tiles that are not yet revealed
//
foreach (var tile in GetAdjacentUnrevealedTiles(point))
{
// Make sure this tile is not a mine
//
if (!tile.IsMine)
{
// Reveal the tile
//
tile.Reveal();
// We also want to check this tiles adjacent unrevealed tiles, add it to our list for checking after this loop completes
//
nextTilesToCheck.Add(tile);
}
}
// Loop through all the new tiles we need to check
//
foreach (var adjacentTile in nextTilesToCheck)
{
// If there are no surrounding mines, lets run this function again for this coordinate
//
if (adjacentTile.SurroundingMinesCount == 0)
{
// Recursion
//
RevealEmptyAdjacentTiles(adjacentTile.Coordinate);
}
}
}
我们只关注尚未揭晓的板块。当检查那些未暴露的相邻图块时,我们会显示该图块(除非它是地雷),随后这些图块将永远不需要再次检查。
你提到的数字(0.5GB 和 5GB)让我担心除了 @joshua-robinson 指出的之外还有其他一些问题。如果我们假设您有一个 100x100 的网格,并且您当前的方法可能会将每 Tile
次添加到队列中 8 次,这意味着每个图块 5GB / (100 * 100 * 8) == 65kB
。对于单个 Tile
,这是 很多 的内存 - 这是假设 GetBunka
实例化一个新的 Tile
对象而不是重复使用相同的 Tile
给定位置上的对象。如果您测试的网格较小,则每个 Tile
的内存量甚至更高。
在不知道 Tile
的内容的情况下,我认为它占用的空间应该远小于 1kB。
但为了解决手头的问题,我们可以使用 HashSet
来跟踪我们已经看过的图块,以避免将它们多次添加到队列中。
private void FloodIterative(Tile tile)
{
Queue<Tile> queue = new Queue<Tile>();
queue.Enqueue(tile);
// Tiles we have had a peak at
// I.e. they were added to the queue at some point
var tilesPeakedAt = new HashSet<Tile>(queue);
while (queue.Count != 0)
{
Tile b = queue.Dequeue();
if (b.isDiscovered) continue;
if (b.type == Tile.Type.Mine || b.type == Bunka.Type.Invalid) continue;
b.isDiscovered = true;
state[b.position.x, b.position.y] = b;
if (b.type == Bunka.Type.Empty)
{
var tilesToCheck = GetAllNeighbors(b)
.Where(t => !tilesPeakedAt.Contains(t));
foreach (var t in tilesToCheck)
{
queue.Enqueue(t);
tilesPeakedAt.Add(t);
}
}
}
}
private Tile[] GetAllNeighbors(Tile b) // Position as input would be enough
{
return new []
{
GetBunka(b.position.x - 1, b.position.y)
GetBunka(b.position.x + 1, b.position.y)
GetBunka(b.position.x, b.position.y - 1)
GetBunka(b.position.x, b.position.y + 1)
GetBunka(b.position.x - 1, b.position.y - 1)
GetBunka(b.position.x + 1, b.position.y + 1)
GetBunka(b.position.x + 1, b.position.y - 1)
GetBunka(b.position.x - 1, b.position.y + 1)
};
}
这应该会将放入队列的图块数量最多减少 8 倍。
我正在尝试制作一个扫雷游戏,我在其中制作了使用队列集合迭代淹没未知图块的方法
private void FloodIterative(Tile tile)
{
Queue<Tile> queue = new Queue<Tile>();
queue.Enqueue(tile);
while (queue.Count != 0)
{
Tile b = queue.Dequeue();
if (b.isDiscovered) continue;
if (b.type == Tile.Type.Mine || b.type == Bunka.Type.Invalid) continue;
b.isDiscovered = true;
state[b.position.x, b.position.y] = b;
if (b.type == Bunka.Type.Empty)
{
queue.Enqueue(GetBunka(b.position.x - 1, b.position.y));
queue.Enqueue(GetBunka(b.position.x + 1, b.position.y));
queue.Enqueue(GetBunka(b.position.x, b.position.y - 1));
queue.Enqueue(GetBunka(b.position.x, b.position.y + 1));
queue.Enqueue(GetBunka(b.position.x - 1, b.position.y - 1));
queue.Enqueue(GetBunka(b.position.x + 1, b.position.y + 1));
queue.Enqueue(GetBunka(b.position.x + 1, b.position.y - 1));
queue.Enqueue(GetBunka(b.position.x - 1, b.position.y + 1));
}
}
}
此方法效果很好,但仅适用于数量有限的未探索图块。如果图块编号太大,我的统一引擎输出:
OutOfMemoryException: 内存不足
System.Collections.Generic.Queue 1[T].SetCapacity(System.Int32 容量)(位于 <5a2009c85b134970925993880e2ecb2e>:0) System.Collections.Generic.Queue 1[T].Enqueue (T item) (at <5a2009c85b134970925993880e2ecb2e>:0)
有什么解决办法吗?我可能需要手动释放内存吗? 非常感谢您。
您似乎为每个取出的物品排队了 8 件物品,并且您排队了重复的物品。在将项目放入队列之前检查它是否已经存在。
这是我不久前制作的扫雷游戏的片段。
了解给定方块周围有多少地雷会有所帮助。这可以在网格的初始构造期间设置。
private void RevealEmptyAdjacentTiles(Point point)
{
// Create a list to store the next tiles we need to check
//
var nextTilesToCheck = new List<Tile>();
// Loop through the adjacent tiles that are not yet revealed
//
foreach (var tile in GetAdjacentUnrevealedTiles(point))
{
// Make sure this tile is not a mine
//
if (!tile.IsMine)
{
// Reveal the tile
//
tile.Reveal();
// We also want to check this tiles adjacent unrevealed tiles, add it to our list for checking after this loop completes
//
nextTilesToCheck.Add(tile);
}
}
// Loop through all the new tiles we need to check
//
foreach (var adjacentTile in nextTilesToCheck)
{
// If there are no surrounding mines, lets run this function again for this coordinate
//
if (adjacentTile.SurroundingMinesCount == 0)
{
// Recursion
//
RevealEmptyAdjacentTiles(adjacentTile.Coordinate);
}
}
}
我们只关注尚未揭晓的板块。当检查那些未暴露的相邻图块时,我们会显示该图块(除非它是地雷),随后这些图块将永远不需要再次检查。
你提到的数字(0.5GB 和 5GB)让我担心除了 @joshua-robinson 指出的之外还有其他一些问题。如果我们假设您有一个 100x100 的网格,并且您当前的方法可能会将每 Tile
次添加到队列中 8 次,这意味着每个图块 5GB / (100 * 100 * 8) == 65kB
。对于单个 Tile
,这是 很多 的内存 - 这是假设 GetBunka
实例化一个新的 Tile
对象而不是重复使用相同的 Tile
给定位置上的对象。如果您测试的网格较小,则每个 Tile
的内存量甚至更高。
在不知道 Tile
的内容的情况下,我认为它占用的空间应该远小于 1kB。
但为了解决手头的问题,我们可以使用 HashSet
来跟踪我们已经看过的图块,以避免将它们多次添加到队列中。
private void FloodIterative(Tile tile)
{
Queue<Tile> queue = new Queue<Tile>();
queue.Enqueue(tile);
// Tiles we have had a peak at
// I.e. they were added to the queue at some point
var tilesPeakedAt = new HashSet<Tile>(queue);
while (queue.Count != 0)
{
Tile b = queue.Dequeue();
if (b.isDiscovered) continue;
if (b.type == Tile.Type.Mine || b.type == Bunka.Type.Invalid) continue;
b.isDiscovered = true;
state[b.position.x, b.position.y] = b;
if (b.type == Bunka.Type.Empty)
{
var tilesToCheck = GetAllNeighbors(b)
.Where(t => !tilesPeakedAt.Contains(t));
foreach (var t in tilesToCheck)
{
queue.Enqueue(t);
tilesPeakedAt.Add(t);
}
}
}
}
private Tile[] GetAllNeighbors(Tile b) // Position as input would be enough
{
return new []
{
GetBunka(b.position.x - 1, b.position.y)
GetBunka(b.position.x + 1, b.position.y)
GetBunka(b.position.x, b.position.y - 1)
GetBunka(b.position.x, b.position.y + 1)
GetBunka(b.position.x - 1, b.position.y - 1)
GetBunka(b.position.x + 1, b.position.y + 1)
GetBunka(b.position.x + 1, b.position.y - 1)
GetBunka(b.position.x - 1, b.position.y + 1)
};
}
这应该会将放入队列的图块数量最多减少 8 倍。