具有重复键的映射实现

136

我想要一个可以有重复键的映射表。

我知道有很多映射表实现(Eclipse 显示了大约50个),所以我相信一定有一个允许这样做的。我知道编写自己的映射表很容易,但我更愿意使用一些现有的解决方案。

可能在 commons-collections 或 google-collections 中有相关的东西?


4
如果您请求与一个键相关联的值,而且这个键在地图中存在多次,那么应该如何处理?应该返回哪个值? - Mnementh
获取可能会抛出异常,我只需要这个映射进行迭代。 - IAdapter
6
如果你只需要进行迭代,为什么一开始要使用映射表呢?可以考虑使用成对列表等其他数据结构。 - Tal Pressman
3
因为我的整个程序已经使用了Map,现在我发现数据可能会有重复的键。如果改用其他方式来实现Map,那么就会非常错误,因为我们只需要五种实现而不是50多种。 - IAdapter
20个回答

94

您正在寻找一个multimap,实际上commons-collections和Guava都有几个实现。Multimaps允许通过维护每个键的值集合来使用多个键,即您可以将单个对象放入映射中,但是检索到的是一个集合。

如果您可以使用Java 5,我建议使用Guava的Multimap,因为它支持泛型。


3
同时,这个Multimap并不像Apache的那个一样自我标榜为Map。 - Kevin Bourrillion
7
请注意,Google Collections已被Guava取代,因此这是Guava版本MultiMap的链接:https://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multimap - Josh Glover
然而,Multimap并不是完全可序列化的,它具有瞬态成员,这使得反序列化实例无用。 - dschulten
@dschulten 嗯,Multimap是一个接口,你没有指定你所指的实现。com.google.common.collect.HashMultimapreadObject/writeObject方法,ArrayListMultimap和Immutable{List,Set}Multimap也有。我认为一个无用的反序列化实例是值得报告的错误。 - nd.
1
Apache Collections 4.0 支持泛型。请访问 https://commons.apache.org/proper/commons-collections/javadocs/api-release/org/apache/commons/collections4/MultiMap.html 了解更多信息。 - kervin
显示剩余2条评论

37

我们不需要依赖于Google Collections外部库。您可以简单地实现以下Map:

Map<String, ArrayList<String>> hashMap = new HashMap<String, ArrayList>();

public static void main(String... arg) {
   // Add data with duplicate keys
   addValues("A", "a1");
   addValues("A", "a2");
   addValues("B", "b");
   // View data.
   Iterator it = hashMap.keySet().iterator();
   ArrayList tempList = null;

   while (it.hasNext()) {
      String key = it.next().toString();             
      tempList = hashMap.get(key);
      if (tempList != null) {
         for (String value: tempList) {
            System.out.println("Key : "+key+ " , Value : "+value);
         }
      }
   }
}

private void addValues(String key, String value) {
   ArrayList tempList = null;
   if (hashMap.containsKey(key)) {
      tempList = hashMap.get(key);
      if(tempList == null)
         tempList = new ArrayList();
      tempList.add(value);  
   } else {
      tempList = new ArrayList();
      tempList.add(value);               
   }
   hashMap.put(key,tempList);
}
请确保对代码进行微调。

21
当然,你不需要依赖Guava的Multimap。使用它只是为了方便你的生活,因为你不必重新实现、测试等等。 - PhiLho
这不允许无缝迭代所有对。肯定还有更多的缺点。我正要建议我的解决方案,它也需要一个额外的类,然后看到@Mnementh的答案就是那样。 - Mark Jeronimus
2
编写基本代码并不总是那么聪明。Google 更有可能拥有更好的测试。 - senseiwu

29
Multimap<Integer, String> multimap = ArrayListMultimap.create();

multimap.put(1, "A");
multimap.put(1, "B");
multimap.put(1, "C");
multimap.put(1, "A");

multimap.put(2, "A");
multimap.put(2, "B");
multimap.put(2, "C");

multimap.put(3, "A");

System.out.println(multimap.get(1));
System.out.println(multimap.get(2));       
System.out.println(multimap.get(3));

输出为:

[A,B,C,A]
[A,B,C]
[A]

注意:我们需要导入库文件。

http://www.java2s.com/Code/Jar/g/Downloadgooglecollectionsjar.htm

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

或者 https://commons.apache.org/proper/commons-collections/download_collections.cgi

import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.map.MultiValueMap;

2
好建议,因为我在项目中使用了Spring,所以最终使用了文档中提到的Spring MultiValueMap风格 http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/MultiValueMap.html。 - ajup

19
你可以在常规HashMap中传递一个值的数组作为值,从而模拟重复的键,由你决定使用哪些数据。
你也可以使用MultiMap,但我个人不喜欢重复的键这个想法。

谢谢!使用 TreeMap<String, ArrayList<MyClass>> 解决了我的重复键需求。 - Joe

11

如果你想遍历一个键值对列表(正如你在评论中所写的),那么使用List或数组会更好。首先将你的键和值组合起来:

public class Pair
{
   public Class1 key;
   public Class2 value;

   public Pair(Class1 key, Class2 value)
   {
      this.key = key;
      this.value = value;
   }

}

用你想要作为键和值的类型替换Class1和Class2。

现在,你可以把它们放进数组或列表中并遍历它们:

Pair[] pairs = new Pair[10];
...
for (Pair pair : pairs)
{
   ...
}

如何实现add()或put()函数。我不想硬编码维度数。 - Amit Kumar Gupta
2
在这种情况下,请使用List。第二个示例更改为List<Pair> pairs = new List<Pair>(); for循环保持不变。您可以使用此命令添加一对:pairs.add(pair); - Mnementh
说实话,这可能是最好的答案。 - PaulBGD

11
这个问题可以使用一个地图条目列表List<Map.Entry<K,V>>来解决。我们不需要使用任何外部库或Map的新实现。可以像这样创建一个地图条目:Map.Entry<String, Integer> entry = new AbstractMap.SimpleEntry<String, Integer>("key", 1);。请注意保留 HTML 标记。

7

这个类已被弃用,现在被称为MultiValueMap。http://commons.apache.org/proper/commons-collections/javadocs/api-3.2.1/org/apache/commons/collections/MultiHashMap.html - Jotschi
Collections 4.0 支持泛型。请参考 https://commons.apache.org/proper/commons-collections/javadocs/api-release/org/apache/commons/collections4/MultiMap.html。 - kervin

4

您可以使用具有自定义比较器的TreeMap,以使每个键与其他键不相等。它还会保留插入顺序,就像LinkedHashMap一样。因此,最终结果就像是允许重复键的LinkedHashMap!

这是一种非常简单的实现,不需要任何第三方依赖项或MultiMaps的复杂性。

import java.util.Map;
import java.util.TreeMap;

...
...

//Define a TreeMap with a custom Comparator
Map<Integer, String> map = new TreeMap<>((a, b) -> 1); // See notes 1 and 2

//Populate the map
map.put(1, "One");
map.put(3, "Three");
map.put(1, "One One");
map.put(7, "Seven");
map.put(2, "Two");
map.put(1, "One One One");
    
//Display the map entries:
map.entrySet().forEach(System.out::println);

//See note number 3 for the following:
Map<Integer, String> sortedTreeMap = map.entrySet().stream()
            .sorted(Map.Entry.comparingByKey())
            .collect(Collectors.toMap(
                Map.Entry::getKey, Map.Entry::getValue,
                (x, y) -> x, () -> new TreeMap<>((a, b) -> 1)
             ));
//Display the entries of this sorted TreeMap: 
sortedTreeMap.entrySet().forEach(System.out::println);

    
...

注意:

  1. 在此比较器的定义中,您还可以使用任何正整数。
  2. 如果您使用任何负整数,则会颠倒映射中的插入顺序。
  3. 如果您还想根据键对这个Map进行排序(这是TreeMap的默认行为),那么您可以在当前Map上执行此操作。

4

从我的错误中学习... 请不要自行实现此功能。 Guava multimap 是最好的选择。

在 multimaps 中常见的改进是禁止重复的键值对。

在您的实现中实施/更改这一点可能很麻烦。

在 Guava 中,它就像这样简单:

HashMultimap<String, Integer> no_dupe_key_plus_val = HashMultimap.create();

ArrayListMultimap<String, Integer> allow_dupe_key_plus_val = ArrayListMultimap.create();

4

不需要花哨的库。地图由唯一键定义,所以不要弯曲它们,使用列表。流很强大。

import java.util.AbstractMap.SimpleImmutableEntry;

List<SimpleImmutableEntry<String, String>> nameToLocationMap = Arrays.asList(
    new SimpleImmutableEntry<>("A", "A1"),
    new SimpleImmutableEntry<>("A", "A2"),
    new SimpleImmutableEntry<>("B", "B1"),
    new SimpleImmutableEntry<>("B", "B1"),
);

就是这样。使用示例:

List<String> allBsLocations = nameToLocationMap.stream()
        .filter(x -> x.getKey().equals("B"))
        .map(x -> x.getValue())
        .collect(Collectors.toList());

nameToLocationMap.stream().forEach(x -> 
do stuff with: x.getKey()...x.getValue()...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接