具有重复键的地图实现


116

我想要一张包含重复键的地图。

我知道有很多地图实现(Eclipse向我展示了大约50个),所以我敢肯定一定有一个允许这种实现的地图。我知道编写自己的地图很容易做到这一点,但是我宁愿使用一些现有的解决方案。

也许在Commons Collections或Google Collections中?


4
这应该如何工作?如果您要求一个与键关联的值,并且该键在映射中多次存在,应该返回哪个值?
Mnementh,2009年

得到可能引发异常,我只需要此映射进行迭代。
IAdapter 2009年

6
如果仅需要迭代,那么为什么首先需要地图?使用成对清单或其他物品...
Tal Pressman,2009年

2
因为我的整个程序已经可以使用Map了,现在我发现数据可能具有重复的键。如果使用Map的方式不对,那么我们将只有5种Map实现,而不是50种以上的实现。
IAdapter 2009年

Answers:


90

您正在搜索多图,而commons-collection和Guava确实都有几种实现方法。多图通过维护每个键的值的集合来允许多个键,即,您可以将单个对象放入地图中,但可以检索一个集合。

如果您可以使用Java 5,则我希望使用Guava,Multimap因为它具有泛型感知能力。


3
而且,此Multimap不会像apache那样伪装成Map。
凯文·布瑞里恩

7
请注意,Google图书收藏已被Guava取代,因此,这里是MultiMap的Guava版本的链接:code.google.com/p/guava-libraries/wiki/…–
Josh Glover

但是,Multimap不能完全序列化,它具有临时成员,这使得反序列化实例
变得

@dschulten好吧,Multimap是一个接口,您没有指定要实现的实现。com.google.common.collect.HashMultimap具有readObject/ writeObject方法,ArrayListMultimap和Immutable {List,Set} Multimap也是如此。我会认为无用的反序列化实例是值得报告的错误。
nd。


35

我们不需要依赖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);
}

请确保微调代码。


14
当然,您无需依赖Guava的Multimap。它只是简化你的生活,因为你不必重新实现它们,测试他们,等等
PhiLho

这不允许在所有对上进行无缝迭代。肯定还有更多的缺点。我正要提出我的解决方案,它也需要额外的一堂课,然后看到@Mnementh的答案就是这样。
Mark Jeronimus

2
编写基本代码并不总是那么聪明。Google更有可能进行更好的测试
senseiwu

26
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

18

您可以简单地在常规HashMap中传递值的值数组,从而模拟重复键,这取决于您决定使用什么数据。

您也可以只使用MultiMap,尽管我自己不喜欢重复键的想法。


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

10

如果要迭代键值对列表(如您在注释中所写),则最好使用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()。我不想硬性化尺寸。
阿米特·库玛·古普塔

2
在这种情况下,请使用列表。第二个示例更改为List <Pair>对= new List <Pair>(); for循环保持不变。您可以使用以下命令添加对:pairs.add(pair);。
Mnementh

老实说,这可能是最好的答案。
PaulBGD 2014年

6

这个问题可以通过地图条目列表解决List<Map.Entry<K,V>>。我们既不需要使用外部库,也不需要使用Map的新实现。可以按以下方式创建地图条目: Map.Entry<String, Integer> entry = new AbstractMap.SimpleEntry<String, Integer>("key", 1);



3

从我的错误中学习...请不要自己实现。番石榴多图是必经之路。

多映射中所需的常见增强功能是不允许重复的键值对。

在您的实现中实现/更改它可能会很烦人。

在番石榴中,它很简单:

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

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

2

对于此问题,我有一个略有不同的变体:需要将两个不同的值与同一键相关联。只是将其发布在这里以防其他人受益,我引入了HashMap作为值:

/* @param frameTypeHash: Key -> Integer (frameID), Value -> HashMap (innerMap)
   @param innerMap: Key -> String (extIP), Value -> String
   If the key exists, retrieve the stored HashMap innerMap 
   and put the constructed key, value pair
*/
  if (frameTypeHash.containsKey(frameID)){
            //Key exists, add the key/value to innerHashMap
            HashMap innerMap = (HashMap)frameTypeHash.get(frameID);
            innerMap.put(extIP, connName+":"+frameType+":"+interfaceName);

        } else {
            HashMap<String, String> innerMap = new HashMap<String, String>();
            innerMap.put(extIP, connName+":"+frameType+":"+interfaceName);
            // This means the key doesn't exists, adding it for the first time
            frameTypeHash.put(frameID, innerMap );
        }
}

在上面的代码中,关键帧ID是从输入文件的每一行的第一个字符串中读取的,frameTypeHash的值是通过拆分剩余的行来构造的,并在文件开始具有多行的一段时间内最初存储为String对象( (具有不同的值)与同一frameID键相关联,因此frameTypeHash被覆盖,最后一行作为该值。我用另一个HashMap对象作为值字段替换了String对象,这有助于维护指向不同值映射的单个键。


2

不需要花哨的库。地图是由唯一键定义的,因此请勿弯曲它们,请使用列表。流是强大的。

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()...

1
class  DuplicateMap<K, V> 
{
    enum MapType
    {
        Hash,LinkedHash
    }

    int HashCode = 0;
    Map<Key<K>,V> map = null;

    DuplicateMap()
    {
        map = new HashMap<Key<K>,V>();
    }

    DuplicateMap( MapType maptype )
    {
        if ( maptype == MapType.Hash ) {
            map = new HashMap<Key<K>,V>();
        }
        else if ( maptype == MapType.LinkedHash ) {
            map = new LinkedHashMap<Key<K>,V>();
        }
        else
            map = new HashMap<Key<K>,V>();
    }

    V put( K key, V value  )
    {

        return map.put( new Key<K>( key , HashCode++ ), value );
    }

    void putAll( Map<K, V> map1 )
    {
        Map<Key<K>,V> map2 = new LinkedHashMap<Key<K>,V>();

        for ( Entry<K, V> entry : map1.entrySet() ) {
            map2.put( new Key<K>( entry.getKey() , HashCode++ ), entry.getValue());
        }
        map.putAll(map2);
    }

    Set<Entry<K, V>> entrySet()
    {
        Set<Entry<K, V>> entry = new LinkedHashSet<Map.Entry<K,V>>();
        for ( final Entry<Key<K>, V> entry1 : map.entrySet() ) {
            entry.add( new Entry<K, V>(){
                private K Key = entry1.getKey().Key();
                private V Value = entry1.getValue();

                @Override
                public K getKey() {
                    return Key;
                }

                @Override
                public V getValue() {
                    return Value;
                }

                @Override
                public V setValue(V value) {
                    return null;
                }});
        }

        return entry;
    }

    @Override
    public String toString() {
        StringBuilder builder = new  StringBuilder();
        builder.append("{");
        boolean FirstIteration = true;
        for ( Entry<K, V> entry : entrySet() ) {
            builder.append( ( (FirstIteration)? "" : "," ) + ((entry.getKey()==null) ? null :entry.getKey().toString() ) + "=" + ((entry.getValue()==null) ? null :entry.getValue().toString() )  );
            FirstIteration = false;
        }
        builder.append("}");
        return builder.toString();
    }

    class Key<K1>
    {
        K1 Key;
        int HashCode;

        public Key(K1 key, int hashCode) {
            super();
            Key = key;
            HashCode = hashCode;
        }

        public K1 Key() {
            return Key;
        }

        @Override
        public String toString() {
            return  Key.toString() ;
        }

        @Override
        public int hashCode() {

            return HashCode;
        }
    }

谢谢@daspilker。我现在只能看到您的编辑。Gud看到有人发现我的摘要值得编辑。
百利·大卫

1
 1, Map<String, List<String>> map = new HashMap<>();

这种冗长的解决方案有很多缺点,并且容易出错。这意味着我们需要为每个值实例化一个Collection,在添加或删除值之前检查其是否存在,在没有剩余值时手动将其删除,等等。

2, org.apache.commons.collections4.MultiMap interface
3, com.google.common.collect.Multimap interface 

java-map-duplicate-keys


1

这样的MultiMap隐含功能怎么样?

public class MultiMap<K, V> extends HashMap<K, Set<V>> {
  private static final long serialVersionUID = 1L;
  private Map<K, Set<V>> innerMap = new HashMap<>();

  public Set<V> put(K key, V value) {
    Set<V> valuesOld = this.innerMap.get(key);
    HashSet<V> valuesNewTotal = new HashSet<>();
    if (valuesOld != null) {
      valuesNewTotal.addAll(valuesOld);
    }
    valuesNewTotal.add(value);
    this.innerMap.put(key, valuesNewTotal);
    return valuesOld;
  }

  public void putAll(K key, Set<V> values) {
    for (V value : values) {
      put(key, value);
    }
  }

  @Override
  public Set<V> put(K key, Set<V> value) {
    Set<V> valuesOld = this.innerMap.get(key);
    putAll(key, value);
    return valuesOld;
  }

  @Override
  public void putAll(Map<? extends K, ? extends Set<V>> mapOfValues) {
    for (Map.Entry<? extends K, ? extends Set<V>> valueEntry : mapOfValues.entrySet()) {
      K key = valueEntry.getKey();
      Set<V> value = valueEntry.getValue();
      putAll(key, value);
    }
  }

  @Override
  public Set<V> putIfAbsent(K key, Set<V> value) {
    Set<V> valueOld = this.innerMap.get(key);
    if (valueOld == null) {
      putAll(key, value);
    }
    return valueOld;
  }

  @Override
  public Set<V> get(Object key) {
    return this.innerMap.get(key);
  }

  @Override
  etc. etc. override all public methods size(), clear() .....

}

0

您能否解释一下您尝试使用重复键实现映射的上下文?我敢肯定会有更好的解决方案。地图旨在保留唯一键,这是有充分理由的。虽然如果您真的想这样做;您始终可以扩展该类,编写一个简单的自定义地图类,该类具有碰撞缓解功能,并使您可以使用相同的键保留多个条目。

注意:您必须实现碰撞缓解功能,以便将碰撞键转换为唯一的“始终”集。一些简单的事情,例如,将键与对象哈希码一起附加?


1
上下文是我认为键将是唯一的,但是事实证明键可能不是唯一的。我不想重构所有东西,因为它不会经常使用。
IAdapter 2009年

我想将一个小的XML文件转换为像datatype这样的hashmap。只是问题是XML文件的结构是不固定的
阿米特·库马尔·古普塔


0

有了一点技巧,您就可以将HashSet与重复的密钥一起使用。警告:这在很大程度上取决于HashSet实现。

class MultiKeyPair {
    Object key;
    Object value;

    public MultiKeyPair(Object key, Object value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public int hashCode() {
        return key.hashCode();
    }
}

class MultiKeyList extends MultiKeyPair {
    ArrayList<MultiKeyPair> list = new ArrayList<MultiKeyPair>();

    public MultiKeyList(Object key) {
        super(key, null);
    }

    @Override
    public boolean equals(Object obj) {
        list.add((MultiKeyPair) obj);
        return false;
    }
}

public static void main(String[] args) {
    HashSet<MultiKeyPair> set = new HashSet<MultiKeyPair>();
    set.add(new MultiKeyPair("A","a1"));
    set.add(new MultiKeyPair("A","a2"));
    set.add(new MultiKeyPair("B","b1"));
    set.add(new MultiKeyPair("A","a3"));

    MultiKeyList o = new MultiKeyList("A");
    set.contains(o);

    for (MultiKeyPair pair : o.list) {
        System.out.println(pair.value);
    }
}

0

如果有重复的键,那么一个键可能对应一个以上的值。显而易见的解决方案是将密钥映射到这些值的列表。

例如在Python中:

map = dict()
map["driver"] = list()
map["driver"].append("john")
map["driver"].append("mike")
print map["driver"]          # It shows john and mike
print map["driver"][0]       # It shows john
print map["driver"][1]       # It shows mike

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.