地图列表:高效实施

时间:2013-10-28 16:37:36

标签: java collections guava space-efficiency

我有创建和使用集合的代码,例如:

List<Map<String, Object>> tableData;

此地图列表中填充了 n 地图,每个地图代表数据库中的一行。每行表示为字段名称和对应于字段的对象之间的映射(在这种情况下类型无关紧要)。某些字段可能会丢失。字段数 m 总是远小于行数( n ≈10000× m )。我需要重复使用相同的集合几次来读取所有行,所以我不能只使用某种惰性迭代器。

是否存在有效的数据结构来存储它?番石榴提供了Table集合,但似乎不符合要求。我正在考虑创建一个界面,如:

interface TableData{
  int size();
  Map<String, Object> get(int i);
  // ... (interators, etc.)
}

然后创建一个使用一个Map<String,List<Object>>的实现,这样我只实例化 m 列表而不是 n 映射并仅在需要时动态创建映射但我想知道是否有更通用的数据结构。

由于

4 个答案:

答案 0 :(得分:3)

首先请确保您确实需要优化。

假设平均不超过50%的列丢失,List<Object[]>是明显的赢家:

class TableDataImpl implements TableData {
    private List<Object[]> data;
    private Map<String, Integer> columnNameToIndexMap;

    public Map<String, Object> get(int i) {
        return new ArrayMap(data.get(i));
    }

    private class ArrayMap implements Map<String, Object> {

        private Object[] row;

        ArrayMap(Object[] row) {
            this.row = row;
        }

        public Object get(String key) {
            Integer index = columnNameToIndexMap.get(key);
            if (index==null) return null;
            return row[index];
       }

       // all the other Map stuff... a lot of code!
    }
}

我不称之为简单,所以请确保你真的需要优化。

否则,假设平均不超过95%的列缺失,则应该采用稍微复杂的构造:对于每一行,使用本地BitSetlong[] )用于存储哪些列存在。这样你只会浪费一个比特而不是Object[]中的整个条目(32或64位)。

这更复杂,因此请确保您确实需要进行优化。

假设许多行共享同一组列,您可以在每行中存储columnNameToIndexMap

答案 1 :(得分:3)

我运行了一些测试(无论如何都没有结论,但非常具有指示性)来建立不同List<Map<String, Object>>实现的内存占用。基线是Java的ArrayList<>,其元素是Guava ImmutableMap的实例。

我比较的实现如下:

  1. 使用Map<String,List<Object>>HashMap s基于ArrayList的实施;
  2. 使用List<Object[]>;
  3. 基于ArrayList的实施
  4. Guava的HashBasedTable<Integer,String,Object>;
  5. Guava的ArrayTable<Integer,String,Object>;
  6. 我的测试包括生成 n 随机行,每行包含 m 列和 k 的“填充因子”,其中填充因子为定义为每行包含所有列的值的概率。为简单起见,这些值是使用Apache Commons RandomStringUtils生成的长度为 l 的随机字符串。

    但是让我们看看结果。 n = 200000, m = 50, l = 10且 k in(1.0,7.5,0.5)我得到了以下记忆足迹作为基线的百分比:

        | k = 1.0  | k = 0.75 | k = 0.5  |
    ----------------------------------------
    1.  |     71 % |     71 % |     71 % |
    2.  |     71 % |     72 % |     73 % |
    3.  |    111 % |    107 % |    109 % |
    4.  |     71 % |     73 % |     76 % |
    

    我尝试将 n 减少到20000,结果大致相同。

    我发现上面的结果非常有趣。首先,看起来没有太多的改进空间超过基线的70%。其次,我惊喜地发现高效的Guava的ArrayTable与这个问题中提出的两个实现一样好。我会继续挖掘更多,但我倾向于解决方案1.

    由于

答案 2 :(得分:0)

好吧,如果将所有表数据同时存储在内存中很重要,那么存储数据结构的方向(作为地图列表或列表地图)不会产生太大差异)。地图列表显然更加直观,所以我会保留它。

如果您担心对象创建和清理的效率,我建议使用对象池。以下是它如何运作的基本概念:

public class TableRowPool {

    private static final int INITIAL_CAPACITY = 10000;

    private Queue<Map<String, Object>> mapObjects;

    public TableRowPool() {
        mapObjects = new LinkedList<Map<String, Object>>();
        for(int i = 0; i < INITIAL_CAPACITY; i++) {
            mapObjects.add(new HashMap<String, Object>());
        }
    }

    public Map<String, Object> getTableRowObject() {
        if(mapObjects.size() == 0) {
            mapObjects.add(new HashMap<String, Object>());
        }
        return mapObjects.remove();
    }

    public void returnTableRowObject(Map<String, Object> obj) {
        mapObjects.add(obj);
    }

}

LinkedList作为队列运行良好,因此对象检索速度很快。如果您希望动态增长,它还可以快速添加新对象。但是,您可能需要根据是否需要线程安全来更改数据结构。

要使用对象池,您可以执行以下操作:

//Load data
while((row = getResultSetRow()) != null) {
    Map<String, Object> rowObj = tableRowPool.getTableRowObject();
    //Fill in data
    myRows.add(rowObj);
}

//... Do all your business logic ...

//Cleanup
for(Map<String, Object> rowObj : myRows) {
    tableRowPool.returnTableRowObject(rowObj);
}
myRows = null;

答案 3 :(得分:0)

如果我有这么大的数据,我担心我会得到OOM,那么我不会找到一个最佳的数据结构来保存这些数据,而是寻找我如何使用SIMD并行或类似Map-Reduce的东西。无论您如何优化数据结构,总是会耗尽内存空间。例如,如果您确实找到了适用于特定机器配置的最佳数据结构,则它可能仍然无法在内存较小的机器中运行。

但是如果您仍然想要坚持当前的方法,那么为什么不能对数据进行规范化,以便您可以通过以下方式表示缺少的字段:'Null'。因此,当您读取数据并创建地图时,为什么不为缺少的字段添加“null”?这样你至少不需要像hashmap这样的键值数据结构,你只需要List<List<Object>>