【问题标题】:Only iterate over a part of a Map仅迭代 Map 的一部分
【发布时间】:2011-10-02 09:54:27
【问题描述】:

我将数据存储在 HashMap 中,我想通过多个线程同时访问它,以拆分对项目完成的工作。

通常(例如使用列表)我会给每个线程一个索引以开始,并且可以像这样轻松地拆分工作:

for(int i = startIndex; i < startIndex+batchSize && i < list.size(); i++)
{
    Item a = list.get(i);
    // do stuff with the Item
}

当然这不适用于 HashMap,因为我无法通过索引访问它。

有没有一种简单的方法可以只遍历地图的一部分?对于这种情况,我是否应该使用其他数据结构?

我阅读了有关 SortedMap 的信息,但它有太多我不需要的开销(对项目进行排序)。我有大量数据,性能至关重要。

任何提示将不胜感激。

【问题讨论】:

  • 您希望地图如何分区?
  • 不确定我是否明白这个问题。 :) 我希望将映射划分为与我拥有的线程数一样多的部分(例如 8 个)。如果可能的话,分区不应该是一项昂贵的操作。
  • atm 我正在处理 100.000 个条目,但它确实是开放式的。该算法也应该适用于更大的尺寸。

标签: java data-structures indexing hashmap iteration


【解决方案1】:

首先,您不应该使用 HashMap,因为迭代顺序是未定义的。要么使用LinkedHashMap,其迭代顺序与插入顺序相同(至少已定义),要么使用TreeMap,其迭代顺序是自然排序顺序。我会推荐 LinkedHashMap,因为插入一个条目会使地图切片变得不可预测。

要分割地图,请使用以下代码:

    LinkedHashMap<Integer, String> map = new LinkedHashMap<Integer, String>();

    for (Map.Entry<Integer, String> entry : new ArrayList<Map.Entry<Integer,String>>(map.entrySet()).subList(start, end)) {
        Integer key = entry.getKey();
        String value = entry.getValue();
        // Do something with the entry
    }

我已经内联了代码,但是展开它相当于:

List<Map.Entry<Integer, String>> entryList = new ArrayList<Map.Entry<Integer,String>>();
entryList.addAll(map.entrySet());
entryList = entryList.subList(start, end); // You provide the start and end index
for (Map.Entry<Integer, String> entry : entryList) ...

【讨论】:

  • TreeMap 不是一个选项,因为项目的排序会影响性能,我不需要对项目进行特殊排序。如果在我使用它时 Map 没有改变,我还应该使用 LinkedHashMap 吗?我不关心项目的顺序,为什么定义它很重要?
  • Anywho,感谢 entryList 的解决方案。将其与 Ross Larson 的想法进行比较,看看哪个执行得更快。 :)
  • 因为如果您在一个线程中要求项目 1 到 5,而在另一个线程中要求项目 6 到 10,您可能会在两者中获得相同的项目 - 迭代顺序没有为 hashmap 定义(尽管它可能在现实中修复 - 你可以试一试)
  • 已经做了,而且它似乎固定在下面。但你是对的,未定义的行为从来都不是一件好事。 LinkedHashMap 就是这样!
【解决方案2】:

如果您只进行几次遍历,或者如果地图没有改变,您可以获得一组键,然后将其发送到数组。从那里它几乎是你的正常方法。但是很明显,如果 HashMap 发生了变化,那么您将不得不重新执行这两个操作,这可能会变得非常昂贵。

【讨论】:

  • 幸运的是 HashMap 不会被线程改变。你的方法听起来不错,假设 toArray() 方法很便宜。会试一试,看看性能有多好,干杯。
【解决方案3】:

使用 HashMap#keySet -> Set#toArray 你会得到一个键的数组。

使用这个数组,您可以像以前一样继续,保留键数组并将它们传递给您的线程。然后每个线程将仅访问它已分配的键,最后您可以仅使用这些键访问 HashMap 的给定分区的条目。

【讨论】:

  • 谢谢!在考虑这个问题之前我也不知道 :) 这就是 SO 的伟大之处——你在思考一个问题时会学到很多东西。我当时想“嗯,如果 Set 有一个 toArray 怎么办?” - 检查了 JavaDoc - 它有:)
【解决方案4】:

除非你的地图很大,否则迭代地图的成本与在另一个线程上启动任务的成本相比是很小的,与你打算做的工作相比是微不足道的。

因此,划分工作的最简单方法可能是将 Map 转换为 Array 并将其分解。

final Map<K, V> map =
final ExecutorServices es = 
final int portions = Runtime.getRuntime().availableProcessors();
final Map.Entry<K,V>[] entries = (Map.Entry<K,V>[]) map.entrySet().toArray(new Map.Entry[map.size()]);
final int portionSize = (map.size() + portions-1)/ portions;

for(int i = 0; i < portions; i++) {
    final int start = i * portionSize;
    final int end = Math.min(map.size(), (i + 1) * portionSize);
    es.submit(new Runnable() {
        public void run() {
            for(int j=start; j<end;j++) {
               Map.Entry<K,V> entry = entries[j];
               // process entry.
            }
        }
    });
}

【讨论】:

    猜你喜欢
    • 2010-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-08
    • 1970-01-01
    • 2020-12-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多