java - 在 Android 中使用 Hashmap 存储伪无限游戏世界时内存不足

标签 java android data-structures hashmap coordinate-systems

继续 a previous question regarding infinite game worlds and coordinate data structures我决定使用 HashMap 。然而,由于我是在 Android 上尝试这个,所以我很快就遇到了问题。

这里是要求的回顾:

  • 伪无限 2D 瓷砖世界。世界将生成为 探索所有 4 个方向 (2D) 所以我们需要一些东西去 负指标。
  • 相邻图 block 需要随机访问 等
  • 需要能够从特定坐标检索图 block ,例如 得到(x,y)

所以,这是我用来创建 600 * 600 个图 block 的 HashMap 的代码 fragment 。

每个 map 图 block 的类:

public class MapTile {
    public int type;

    public MapTile(int type) {
        this.type = type;
    }

    public boolean is(int type) {
        return (type==this.type);
    }
}

生成大 map 的测试代码:

long startTime;
long lastTime;    
map = new HashMap<Point, MapTile>();
int x;
int y;
int tile;
startTime = System.currentTimeMillis();
lastTime = startTime;
for (x=-300;x<=300;x++) {
    for (y=-300;y<=300;y++) {
        tile = (x+y)%2; //Just so I differentiate the tiles
        this.map.put(new Point(x,y), new MapTile(tile));

    }
    // Logging each row
    Log.d(this.getClass().toString(), "Generated x:" + x + " Time in ms:"+(System.currentTimeMillis() - lastTime));
    lastTime = System.currentTimeMillis();

}

因此,当我在 Java 中运行类似代码时。它成功完成(虽然吃了大量内存)。没问题。

但是,当我尝试在高端设备上的 Android 中执行此操作时,它实际上在达到 x = 190 标记(通常)之前就停止了。垃圾收集器开始运行,随后出现无限循环:

** snip **
05-26 15:52:19.565  10758-10758/com.ayk.test04        D/class com.ayk.test04.WorldMap2D: Generated x:187 Time in ms:2
05-26 15:52:19.570  10758-10758/com.ayk.test04        D/class com.ayk.test04.WorldMap2D: Generated x:188 Time in ms:6
05-26 15:52:19.575  10758-10758/com.ayk.test04        D/class com.ayk.test04.WorldMap2D: Generated x:189 Time in ms:2
05-26 15:52:19.995  10758-10760/com.ayk.test04        I/dalvikvm-heap: Clamp target GC heap from 66.002MB to 64.000MB
05-26 15:52:19.995  10758-10760/com.ayk.test04        D/dalvikvm: GC_CONCURRENT freed 3K, 2% free 64797K/65543K, paused 4ms+30ms, total 429ms
05-26 15:52:19.995  10758-10758/com.ayk.test04        D/dalvikvm: WAIT_FOR_CONCURRENT_GC blocked 421ms
05-26 15:52:20.335  10758-10758/com.ayk.test04        I/dalvikvm-heap: Clamp target GC heap from 66.002MB to 64.000MB
05-26 15:52:20.335  10758-10758/com.ayk.test04        D/dalvikvm: GC_FOR_ALLOC freed 0K, 2% free 64797K/65543K, paused 341ms, total 341ms
05-26 15:52:20.335  10758-10758/com.ayk.test04        I/dalvikvm-heap: Clamp target GC heap from 64.002MB to 64.000MB
05-26 15:52:20.335  10758-10758/com.ayk.test04        I/dalvikvm-heap: Grow heap (frag case) to 64.000MB for 16-byte allocation
05-26 15:52:20.695  10758-10760/com.ayk.test04        I/dalvikvm-heap: Clamp target GC heap from 66.002MB to 64.000MB
05-26 15:52:20.695  10758-10760/com.ayk.test04        D/dalvikvm: GC_CONCURRENT freed 0K, 2% free 64797K/65543K, paused 12ms+2ms, total 358ms
05-26 15:52:20.695  10758-10758/com.ayk.test04        D/dalvikvm: WAIT_FOR_CONCURRENT_GC blocked 332ms
** thousands of these... snip **

所以,差不多,我被困在这里了。
我的瓷砖现在只包含一个 int 变量。这是准系统。所以我明白了,创建一个包含 50.000 个左右对象的 HashMap 在技术上是不可能的?这对我来说似乎有点小。

我想尝试的是:

  • 仅将 map 的 Activity 区域保留在内存中,同时将其余部分卸载到存储中。虽然这可能会解决内存问题,但恐怕在访问 Activity 区域之外的图 block 时可能会导致性能问题。
  • 有人建议使用 block (ala Minecraft)。所以我保留了一个 HashMap 的 HashMap ?行得通吗?如果可以,我该怎么做?
  • 我是否完全偏离了目标并且以错误的方式看待它?

感谢您提供的任何帮助。

更新

你好。在您的帮助下,我决定继续使用数据库。它的工作效率更高,到目前为止,我还没有遇到任何问题。对于后代,这是它的工作原理:

数据库

在 Android 中工作,我已经设置了一个 SQLite 数据库和表来存储每个图 block 。起初,我决定在每次生成图 block 时运行插入查询。这很快就变成了另一个性能瓶颈,因为每个 INSERT 都需要 2 毫秒多一点的时间才能完成,即使是最小的 300x300 切片生成过程也会遇到麻烦。我不得不使用多个 INSERT 查询,该查询采用多个图 block 并使用单个查询存储它们。因此,我创建了一个“图 block 缓冲区”,其中每个图 block 都存储了它们的坐标:

//A linked list of integers is good enough. 
//The first 2 are X and Y coordinates, the third one is the tile type. 
//I can add more fields if needed.
this.tileDBbuffer = new LinkedList<Integer[]>();

缓冲区的填充方式如下:

void addTileBuffer(int x,int y, MapTile map_tile) {
    int maxsize = 10000;
    Integer[] tile_data;
    tile_data = new Integer[3];
    tile_data[0] = x;
    tile_data[1] = y;
    tile_data[2] = map_tile.type;
    if (this.tileDBbuffer.size()>= maxsize) {
        Log.d(this.getClass().toString(), "Buffer filled up... Saving");
        savemapbuffer();
    }
    this.tileDBbuffer.add(tile_data);
}

一旦缓冲区达到一定大小(或瓦片生成结束),“savemapbuffer()”函数就会运行,基本上首先调用此函数,然后清除缓冲区:

public void addTileBulk(LinkedList<Integer[]> map_tiles) {
    Integer[] tile_data = new Integer[3];

    if (db == null) db = this.getWritableDatabase();

    String sql = "INSERT INTO "+TABLE_NAME_MAP+" ("+COLUMN_NAME_MAP_X+", "+COLUMN_NAME_MAP_Y+", "+COLUMN_NAME_MAP_TILE_TYPE+") VALUES (?,?,?)";
    db.beginTransaction();
    SQLiteStatement stmt = db.compileStatement(sql);
    try {
        while (!map_tiles.isEmpty()) {
            tile_data = map_tiles.pop();
            stmt.bindLong(1,tile_data[0]);
            stmt.bindLong(2,tile_data[1]);
            stmt.bindLong(3,tile_data[2]);
            stmt.execute();
        }
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();

    }

    map_tiles.clear();
}

正在使用 HashMap

此后不久,绘制 map 时出现问题。绘图是在另一个线程中完成的,因此在生成新图 block 的同时迭代它会引发许多奇怪的错误。在使用“ map 是否繁忙” boolean 值进行了一些测试后,我最终使用了 ConcurrentHashmap 而不是常规的 Hashmap。它奏效了。

block

在那之后,我最终还是需要使用“ block ”。管理 map 的离散部分比根据玩家距离估计加载哪些图 block 更容易。所以现在,每个 HashMap 元素都是一个固定的二维整数数组,包含瓦片。我称它们为细胞

map = new ConcurrentHashMap<Point, MapCell>();

一个单元格是这样的:

public class MapCell {
    public int x,y;
    private static int cell_size = 32;
    public MapTile[][] tiles = new MapTile[cell_size][cell_size];
}

现在。我要去 Perlin 噪声和 map 生成冒险:) 下次见。

最佳答案

您可能不得不选择将子集保留在内存中的第一个选项,尤其是当您的游戏世界变得更大时。为了尽量减少性能损失,您可以考虑创建 30x30 子图 block 之类的东西。在任何时候,保持当前子图加载,同时在后台加载 8 个相邻的 30x30 子图。当玩家角色在 subtile 之间转换时,它会加载得很快,因为它已经加载了,然后当他正在探索新的 subtile 时,您可以将下一个加载到内存中,等等。

如果您遵循 subtiles 策略(subtile 边界对齐到 30 的倍数),则不必经常检查,但只有在您越过 subtile 边界时才检查。此外,通过一次加载 30x30,您可以最大限度地减少数据库或文件访问。您也不需要使用 HashMap:将每个子图 block 表示为 30x30 数组,并存储当前中心子图 block 的左上角坐标。然后,如果需要,您应该能够进行一些数学运算以检索 90x90 附近的相关图 block 。

关于java - 在 Android 中使用 Hashmap 存储伪无限游戏世界时内存不足,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16759445/

相关文章:

java - 可以使用 8.4 和 9.0 postgres( hibernate )库构建一个 .war 文件吗?

java - android ListView onclick

java - 用Java创建数据结构

algorithm - B-Tree和Trie搜索速度对比

java - 如何用不同类的对象填充数组?详情如下

java - 什么是 Resteasy 3.X PreProcessInterceptor 的正确替代品?

java - 我不确定如何在我的 Java 代码中正确地舍入它

c# - Xamarin Android 警报管理器问题

android - 在 ActionBar 顶部显示 NavigationDrawer

java - 寻找数字幂的有效方法的时间复杂度