【问题标题】:Iterate big hashmap in parallel并行迭代大哈希图
【发布时间】:2018-06-27 16:49:47
【问题描述】:

我有一个链接的 hashmap,它最多可以包含 300k 条记录。我想并行迭代这张地图以提高性能。该函数遍历向量映射并找到给定向量与映射中所有向量的点积。还要根据日期值进行另一项检查。该函数返回一个嵌套的哈希图。 T

这是使用迭代器的代码:

public HashMap<String,HashMap<String,Double>> function1(String key, int days) {
    LocalDate date = LocalDate.now().minusDays(days);
    HashMap<String,Double> ret = new HashMap<>();
    HashMap<String,Double> ret2 = new HashMap<>();
    OpenMapRealVector v0 = map.get(key).value;
    for(Map.Entry<String, FixedTimeHashMap<OpenMapRealVector>> e: map.entrySet()) {
        if(!e.getKey().equals(key)) {
            Double d = v0.dotProduct(e.getValue().value);
            d = Double.parseDouble(new DecimalFormat("###.##").format(d));
            ret.put(e.getKey(),d);
            if(e.getValue().date.isAfter(date)){
                ret2.put(e.getKey(),d);
            }
        }
    }
    HashMap<String,HashMap<String,Double>> result = new HashMap<>();
    result.put("dot",ret);
    result.put("anomaly",ret2);
    return result;
}

更新: 我查看了 Java 8 流,但是在使用并行流时遇到了 CastException 和 Null 指针异常,因为该映射正在其他地方进行修改。

代码:

public HashMap<String,HashMap<String,Double>> function1(String key, int days) {
    LocalDate date = LocalDate.now().minusDays(days);
    HashMap<String,Double> ret = new HashMap<>();
    HashMap<String,Double> ret2 = new HashMap<>();
    OpenMapRealVector v0 = map.get(key).value;
    synchronized (map) {
        map.entrySet().parallelStream().forEach(e -> {
            if(!e.getKey().equals(key)) {
                Double d = v0.dotProduct(e.getValue().value);
                d = Double.parseDouble(new DecimalFormat("###.##").format(d));
                ret.put(e.getKey(),d);
                if(e.getValue().date.isAfter(date)) {
                    ret2.put(e.getKey(),d);
                }
            }
        });
    }
}

我已经同步了地图的使用,但它仍然给我以下错误:

java.util.concurrent.ExecutionException: java.lang.ClassCastException
Caused by: java.lang.ClassCastException
Caused by: java.lang.ClassCastException: java.util.HashMap$Node cannot be cast to java.util.HashMap$TreeNode

另外,我在想我是否应该将地图分成多个部分并使用不同的线程并行运行每个部分?

【问题讨论】:

  • “我研究了 Java 8 流,但无法获得此函数的并行流实现。” 向我们展示您的尝试,以便我们更好地帮助您找出什么你做错了。
  • @Andreas ,我已经更新了问题以显示确切的问题。
  • 错误是因为parallel = multi-threaded,并且您在HashMap 上执行多线程ret.put(...),这不是线程安全的对象。将 retret2 更改为 ConcurrentHashMap 将解决该问题,然后将返回值更改为 Map&lt;String, Map&lt;String, Double&gt;&gt;
  • 谢谢@Andreras,我一直在考虑各种事情,但从未检查过 ret 和 ret2。这已经解决了问题。
  • Mehdi 接受的解决方案解决了问题中的问题。 Ravindra 的答案是完整的 Java 8 解决方案。

标签: java hashmap java-stream java-threads


【解决方案1】:

您需要从地图中检索Set&lt;Map.Entry&lt;K, V&gt;&gt;

以下是在 Java8 中使用并行流对 Map 进行迭代的方法:

Map<String, String> myMap = new HashMap<> ();
myMap.entrySet ()
    .parallelStream ()
    .forEach (entry -> {
        String key = entry.getKey ();
        String value = entry.getValue ();
        // here add whatever processing you wanna do using the key / value retrieved
        // ret.put (....);
        // ret2.put (....)
    });

澄清:

映射retret2 应声明为ConcurrentHashMaps 以​​允许来自多个线程的并发插入/更新。

所以2张地图的声明变成:

Map<String,Double> ret = new ConcurrentHashMap<> ();
Map<String,Double> ret2 = new ConcurrentHashMap<> ();

【讨论】:

  • 当然,这样做需要retret2并发映射(或同步,但并发更好)。
  • 好点@Andreas!我会根据您的说明更新我的答案。
【解决方案2】:

使用 Java 8 的一种可能解决方案是,

Map<String, Double> dotMap = map.entrySet().stream().filter(e -> !e.getKey().equals(key))
        .collect(Collectors.toMap(Map.Entry::getKey, e -> Double
                .parseDouble(new DecimalFormat("###.##").format(v0.dotProduct(e.getValue().value)))));
Map<String, Double> anomalyMap = map.entrySet().stream().filter(e -> !e.getKey().equals(key))
        .filter(e -> e.getValue().date.isAfter(date))
        .collect(Collectors.toMap(Map.Entry::getKey, e -> Double
                .parseDouble(new DecimalFormat("###.##").format(v0.dotProduct(e.getValue().value)))));
result.put("dot", dotMap);
result.put("anomaly", anomalyMap);

更新

这里有更优雅的解决方案,

Map<String, Map<String, Double>> resultMap = map.entrySet().stream().filter(e -> !e.getKey().equals(key))
        .collect(Collectors.groupingBy(e -> e.getValue().date.isAfter(date) ? "anomaly" : "dot",
                Collectors.toMap(Map.Entry::getKey, e -> Double.parseDouble(
                        new DecimalFormat("###.##").format(v0.dotProduct(e.getValue().value))))));

这里我们首先根据异常或点对它们进行分组,然后使用下游的Collector 为每个组创建一个Map。我还根据以下建议更新了.filter() 标准。

【讨论】:

  • 这可能是一个更干净的解决方案,除了你应该从dotMap 构建anomalyMap 以避免重复计算。
  • @DidierL : 但是点图没有日期值,这是异常图所需要的。
  • 确实应该取自原图。比较两种解决方案的效率会很好。
  • @DidierL 是的,确实如此,如果可能的话,我还必须将点图和异常图解耦以实现更好的设计。我会调查两者并检查性能。
  • 我假设你的意思是 filter(e -&gt; ! e.getKey().equals(key)) 在两个 中(添加了缺少的 not 运算符)。否则你还不如做一个map.get(key) 而根本不迭代。
猜你喜欢
  • 2012-12-27
  • 1970-01-01
  • 2011-02-28
  • 2014-12-15
  • 2017-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多