关于java:如何组合包含相同类型的两个HashMap对象?

How can I combine two HashMap objects containing the same types?


我有两个HashMap对象定义如下:

1
2
HashMap<String, Integer> map1 = new HashMap<String, Integer>();
HashMap<String, Integer> map2 = new HashMap<String, Integer>();


我还有第三个HashMap对象:

1
HashMap<String, Integer> map3;


如何将map1map2合并为map3


1
2
3
4
map3 = new HashMap<>();

map3.putAll(map1);
map3.putAll(map2);



如果您知道没有重复键,或者您希望map2中的值覆盖map1中的重复键值,则可以只写

1
2
map3 = new HashMap<>(map1);
map3.putAll(map2);


如果需要更多地控制值的组合方式,可以使用在Java 8中添加的Map.merge,它使用用户提供的BiFunction来合并重复键的值。 merge对单个键和值进行操作,因此您需要使用循环或Map.forEach。这里我们连接重复键的字符串:

1
2
3
4
5
map3 = new HashMap<>(map1);
for (Map.Entry<String, String> e : map2.entrySet())
    map3.merge(e.getKey(), e.getValue(), String::concat);
//or instead of the above loop
map2.forEach((k, v) -> map3.merge(k, v, String::concat));


如果您知道没有重复的密钥并且想要强制执行它,则可以使用抛出AssertionError的合并函数:

1
2
3
map2.forEach((k, v) ->
    map3.merge(k, v, (v1, v2) ->
        {throw new AssertionError("duplicate values for key:"+k);}));


从这个特定问题退一步,Java 8流库提供了toMapgroupingBy收集器。如果您在循环中反复合并映射,则可以重构计算以使用流,这既可以澄清您的代码,又可以使用并行流和并发收集器实现轻松并行。



使用Java 8 Stream API的单线程:

1
2
map3 = Stream.of(map1, map2).flatMap(m -> m.entrySet().stream())
       .collect(Collectors.toMap(Entry::getKey, Entry::getValue))


此方法的好处之一是能够传递合并函数,该函数将处理具有相同键的值,例如:

1
2
map3 = Stream.of(map1, map2).flatMap(m -> m.entrySet().stream())
       .collect(Collectors.toMap(Entry::getKey, Entry::getValue, Math::max))



用于合并两个映射的Java 8替代单行程序:

1
defaultMap.forEach((k, v) -> destMap.putIfAbsent(k, v));


与方法参考相同:

1
defaultMap.forEach(destMap::putIfAbsent);


或者使用第三张地图的原始地图解决方案的idemponent:

1
2
Map<String, Integer> map3 = new HashMap<String, Integer>(map2);
map1.forEach(map3::putIfAbsent);


这里有一种方法可以将两个映射合并到具有最少可能的中间复制操作的Guava的快速不可变映射中:

1
2
3
4
ImmutableMap.Builder<String, Integer> builder = ImmutableMap.<String, Integer>builder();
builder.putAll(map1);
map2.forEach((k, v) -> {if (!map1.containsKey(k)) builder.put(k, v);});
ImmutableMap<String, Integer> map3 = builder.build();


有关两个映射中存在的值需要与映射函数组合的情况,另请参阅使用Java 8合并两个映射。



如果您的最终地图不需要可变性,那么Guava的ImmutableMap及其BuilderputAll方法,与Java的Map接口方法相比,可以链接。


使用示例:

1
2
3
4
5
6
Map<String, Integer> mergeMyTwoMaps(Map<String, Integer> map1, Map<String, Integer> map2) {
  return ImmutableMap.<String, Integer>builder()
      .putAll(map1)
      .putAll(map2)
      .build();
}


当然,这个方法可以更通用,使用varargs并从参数等循环到putAll Maps但我想展示一个概念。


此外,ImmutableMap及其Builder几乎没有限制(或者可能是特征?):

  • 它们是空的敌对(throw NullPointerException - 如果map中的任何键或值为null)
  • Builder不接受重复键(如果添加了重复键,则抛出IllegalArgumentException)。


HashMap有一个putAll方法。


http://download.oracle.com/javase/6/docs/api/java/util/HashMap.html



您可以将Collection.addAll()用于其他类型,例如ListSet等。对于Map,您可以使用putAll



用于组合两个可能共享公共密钥的映射的通用解决方案:


到位:

1
2
3
4
public static <K, V> void mergeInPlace(Map<K, V> map1, Map<K, V> map2,
        BinaryOperator<V> combiner) {
    map2.forEach((k, v) -> map1.merge(k, v, combiner::apply));
}


返回新地图:

1
2
3
4
5
6
public static <K, V> Map<K, V> merge(Map<K, V> map1, Map<K, V> map2,
        BinaryOperator<V> combiner) {
    Map<K, V> map3 = new HashMap<>(map1);
    map2.forEach((k, v) -> map3.merge(k, v, combiner::apply));
    return map3;
}


您可以使用HashMap>合并两个散列图,并避免丢失与同一个密钥配对的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
HashMap<String, Integer> map1 = new HashMap<>();
HashMap<String, Integer> map2 = new HashMap<>();
map1.put("key1", 1);
map1.put("key2", 2);
map1.put("key3", 3);
map2.put("key1", 4);
map2.put("key2", 5);
map2.put("key3", 6);
HashMap<String, List<Integer>> map3 = new HashMap<>();
map1.forEach((str, num) -> map3.put(str, new ArrayList<>(Arrays.asList(num))));
//checking for each key if its already in the map, and if so, you just add the integer to the list paired with this key
for (Map.Entry<String, Integer> entry : map2.entrySet()) {
    Integer value = entry.getValue();
    String key = entry.getKey();
    if (map3.containsKey(key)) {
        map3.get(key).add(value);
    } else {
        map3.put(key, new ArrayList<>(Arrays.asList(value)));
    }
}
map3.forEach((str, list) -> System.out.println("{" + str +":" + list +"}"));


输出:

1
2
3
{key1: [1, 4]}
{key2: [2, 5]}
{key3: [3, 6]}


我经常使用一个小片段来创建其他地图的地图:

1
2
3
4
5
6
7
8
9
static public <K, V> Map<K, V> merge(Map<K, V>... args) {
    final Map<K, V> buffer = new HashMap<>();

    for (Map m : args) {
        buffer.putAll(m);
    }

    return buffer;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    HashMap<Integer,String> hs1 = new HashMap<>();
    hs1.put(1,"ram");
    hs1.put(2,"sita");
    hs1.put(3,"laxman");
    hs1.put(4,"hanuman");
    hs1.put(5,"geeta");

    HashMap<Integer,String> hs2 = new HashMap<>();
    hs2.put(5,"rat");
    hs2.put(6,"lion");
    hs2.put(7,"tiger");
    hs2.put(8,"fish");
    hs2.put(9,"hen");

    HashMap<Integer,String> hs3 = new HashMap<>();//Map is which we add

    hs3.putAll(hs1);
    hs3.putAll(hs2);

    System.out.println(" hs1 :" + hs1);
    System.out.println(" hs2 :" + hs2);
    System.out.println(" hs3 :" + hs3);


不会添加重复的项目(即重复的键),因为当我们打印hs3时,我们将只得到一个值5,它将是最后一个值,它将是鼠标。
** [Set具有不允许重复键的属性,但值可以重复]



你可以使用--addAll方法


http://download.oracle.com/javase/6/docs/api/java/util/HashMap.html


但是总有这个问题 - 如果你的两个哈希映射具有相同的任何键 - 那么它将使用第二个哈希映射中的键值覆盖第一个哈希映射中的键值。


为了更安全 - 更改键值 - 您可以在键上使用前缀或后缀 - (第一个哈希映射的不同前缀/后缀和第二个哈希映射的不同前缀/后缀)