在 Java(或 Scala)中迭代 HashMap 的 HashMap

Posted

技术标签:

【中文标题】在 Java(或 Scala)中迭代 HashMap 的 HashMap【英文标题】:Iterating over a HashMap of HashMaps in Java (or Scala) 【发布时间】:2011-03-16 07:22:55 【问题描述】:

我创建了一个类 Foo,它具有返回 Array<Int> 的方法 toArray()

现在,我有一个 HashMap 将 Strings 映射到 HashMaps,它将 Objects 映射到 Foo。那就是:

HashMap<String,HashMap<Object,Foo>>

我想创建一个新类型的对象:

HashMap<String,HashMap<Object,Array<Int>>>

这是通过为原始 HashMAp 中的每个元素 Foo 调用函数 toArray() 获得的。

为此,我通常会这样做:

    public static HashMap<String,HashMap<Object,Array<Int>>> changeMap(Map mpOld) 
        Object key2;
        String key1;
        Iterator it2;
        HashMap<String,HashMap<Object,Array<Int>>> mpNew= 
            new HashMap<String,HashMap<Object,Array<Int>>>()
        Iterator it1 = mpOld.keySet().iterator();
        while (it1.hasNext()) 
            key1=it1.next();
            it2= mpOld.get(key1).keySet().iterator();
            mpNew.put(key1,new HashMap<Object,Array<Int>>())
            while (it2.hasNext()) 
                key2=it2.next();
                mpNew.get(key1).put(key2,mpOld.get(key1).get(key2).toArray());
                //TODO clear entry mpOld.get(key1).get(key2)
            
            //TODO clear entry mpOld.get(key1)
        
        return mpNew;
    

类似的代码可以正常工作,但是 HashMap 的大小太大而无法在内存中容纳其中两个。如您所见,我添加了两个要清除某些条目的点。问题是,如果这样做,我会收到并发错误,或者迭代器循环会终止。

我想知道是否有更好的方法来遍历地图并复制信息。

另外,我在一个 Scala 项目中工作,但在这里我必须使用 Java 类型来解决一些兼容性问题。虽然Java.util.HashMap 不是迭代器,但也许 Scala 有一些隐藏的功能来处理这个问题?

谢谢,

【问题讨论】:

【参考方案1】:

迭代器提供了remove(..) 方法,可以安全地删除之前访问过的项目。遍历映射的键/值条目,转换它们并将它们添加到新映射中,并在你去的时候删除旧的。

/**
 * Transfers and converts all entries from <code>map1</code> to 
 * <code>map2</code>.  Specifically, the @link Foo objects of the 
 * inner maps will be converted to integer arrays via @link Foo#toArray.
 * 
 * @param map1 Map to be emptied.
 * @param map2 Receptacle for the converted entries.
 */
private static void transfer(Map<String, Map<Object, Foo>> map1
        , Map<String, Map<Object, int[]>> map2) 

    final Iterator<Entry<String, Map<Object, Foo>>> mapIt
        = map1.entrySet().iterator();
    while (mapIt.hasNext()) 
        final Entry<String, Map<Object, Foo>> mapEntry = mapIt.next();
        mapIt.remove();
        final Map<Object, int[]> submap = new HashMap<Object,int[]>();
        map2.put(mapEntry.getKey(), submap);
        final Iterator<Entry<Object,Foo>> fooIt 
            = mapEntry.getValue().entrySet().iterator();
        while (fooIt.hasNext()) 
            final Entry<Object,Foo> fooEntry = fooIt.next();
            fooIt.remove();
            submap.put(fooEntry.getKey(), fooEntry.getValue().toArray());
        
    

【讨论】:

【参考方案2】:

我没有时间检查它,但我想这样的东西应该适用于 scala 地图(假设你使用的 scala 2.8 终于来了):

mpO.mapValues(_.mapValues(_.toArray))

它将使用您的外部地图,并将所有内部地图“替换”为新地图,其中值是 Int 数组。键和地图的一般“结构”保持不变。根据scaladoc“生成的地图包装了原始地图而不复制任何元素。”,所以它不会是真正的替代品。

如果你也这样做

import scala.collection.JavaConversions._

那么 java 映射可以像 scala 映射一样使用:JavaConversions 包含一堆可以在 scala 和 java 集合之间转换的隐式方法。

顺便说一句,使用 Map >> 最后可能不太方便,如果我是你,我会考虑引入一些可以隐藏此构造复杂性的类。

编辑反映您的评论

import scala.collection.JavaConversions._
import java.util.Collections._

object MapValues 
  def main(args: Array[String]) 
    val jMap = singletonMap("a",singletonMap("b", 1))
    println(jMap)
    println(jMap.mapValues(_.mapValues(_+1)))
  

打印:

a=b=1 地图(a -> 地图(b -> 2))

显示隐式很好地应用于外部和内部地图。这就是 JavaConversions 对象的目的:即使您有一个 java 集合,您也可以将它用作类似的 scala 类(具有增强的特性)。 您无需执行任何其他操作,只需导入 JavaConversions._

【讨论】:

谢谢,虽然我在项目中使用了 Scala,但 HashMap 是 Java HashMap,所以你不能在它们上调用 mapVALues。有没有办法使用 JavaConversions 来解决这个问题?【参考方案3】:

例如考虑字符串键;让我们调用输入数据Map&lt;String, Map&lt;String, Object&gt;&gt; data

for (Entry<String, Map<String, Tuple>> entry : data.entrySet()) 
  String itemKey = entry.getKey();
  for (Entry<String, Object> innerEntry : entry.getValue().entrySet()) 
    String innerKey = innerEntry.getKey();
    Object o = innerEntry.getValue();
    // whatever, here you have itemKey, innerKey and o
  

【讨论】:

【参考方案4】:

集合由地图支持,因此对地图的更改会反映在集合中,反之亦然。如果在对集合进行迭代时修改了映射(通过迭代器自己的删除操作除外),则迭代的结果是不确定的。该集合支持元素移除,即通过 Iterator.remove、Set.remove、removeAll、retainAll 和 clear 操作从映射中移除对应的映射。

为什么不在迭代器上调用remove () 方法或set.remove (iterator.next ()),其中iterator.next () 返回键,set 是键集,迭代器是它的迭代器。

PS:也尝试重构您的数据结构,也许是一些处理数据检索的中间类?以数组为值的地图中的地图不会说明任何内容并且难以跟踪。

【讨论】:

以上是关于在 Java(或 Scala)中迭代 HashMap 的 HashMap的主要内容,如果未能解决你的问题,请参考以下文章

在 Scala 中使用索引进行高效迭代

在scala中读取文本文件,一行接一行,不迭代

梯度迭代树(GBDT)算法原理及Spark MLlib调用实例(Scala/Java/python)

Java 集合之HashSet常用方法实例介绍

快学Scala 第五课 (构造映射,获取映射值,更新映射值,迭代映射,与Java互操作)

如何折叠Scala迭代器并获得延迟计算的序列作为结果?