【问题标题】:Map implementation with duplicate keys具有重复键的映射实现
【发布时间】:2010-11-06 23:18:24
【问题描述】:

我想要一张带有重复键的地图。

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

可能在 commons-collections 或 google-collections 中?

【问题讨论】:

  • 这应该如何工作?如果你要求一个键关联的值,并且这个键在 map 中存在多次,应该返回哪个值?
  • get 可能会抛出异常,我只需要此地图进行迭代。
  • 如果你只需要它进行迭代,为什么你首先需要一张地图?使用对或其他东西的列表...
  • 因为我的整个程序已经使用 Map,现在我发现数据可能有重复的键。如果以不同的方式使用 Map 会非常错误,我们将只有 5 个 Map 实现,而不是 50+。

标签: java duplicates guava multimap


【解决方案1】:

【讨论】:

【解决方案2】:

您可以将 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. 您还可以在此处使用任何正整数代替比较器定义中的 1。
  2. 如果您改用任何负整数,则会反转地图中的插入顺序。
  3. 如果您还想根据键对该地图进行排序(这是 TreeMap 的默认行为),则可以在当前地图上执行此操作。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      这样的 MultiMap impl 怎么样?

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

      【讨论】:

        【解决方案5】:
         1, Map<String, List<String>> map = new HashMap<>();
        

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

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

        java-map-duplicate-keys

        【讨论】:

          【解决方案6】:
          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;
          

          【讨论】:

          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

            您正在搜索一个多图,事实上,commons-collections 和 Guava 都有几个实现。 Multimaps 通过为每个键维护一个值集合来允许多个键,即您可以将单个对象放入映射中,但您可以检索一个集合。

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

            【讨论】:

            • 另外,这个 Multimap 不像 apache 那样伪装成 Map。
            • 请注意,Google Collections 已被 Guava 取代,因此这里是 Guava 版本的 MultiMap 的链接:code.google.com/p/guava-libraries/wiki/…
            • 但是,Multimap 不是完全可序列化的,它具有使反序列化实例无用的瞬态成员
            • @dschulten 好吧,Multimap 是一个接口,您没有指定您的意思是哪个实现。 com.google.common.collect.HashMultimapreadObject/writeObject 方法,ArrayListMultimap 和 Immutable{List,Set}Multimap 也是如此。我会认为一个无用的反序列化实例是一个值得报告的错误。
            【解决方案9】:
            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。我现在只看到你的编辑。很高兴看到有人发现我的 sn-p 值得编辑。
            【解决方案10】:

            我用过这个:

            java.util.List&lt;java.util.Map.Entry&lt;String,Integer&gt;&gt; pairList= new java.util.ArrayList&lt;&gt;();

            【讨论】:

              【解决方案11】:

              从我的错误中吸取教训...请不要自行实施。 Guava 多图是要走的路。

              多映射中需要的一个常见增强功能是禁止重复的键值对。

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

              在番石榴中它很简单:

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

              【讨论】:

                【解决方案12】:

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

                例如在 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
                

                【讨论】:

                  【解决方案13】:

                  通过一些技巧,您可以将 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);
                      }
                  }
                  

                  【讨论】:

                    【解决方案14】:

                    我对这个问题有一个稍微不同的变体:需要将两个不同的值与同一个键相关联。只是在这里发布它以防它帮助其他人,我已经引入了一个 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 );
                            }
                    }
                    

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

                    【讨论】:

                      【解决方案15】:

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

                      请确保对代码进行微调。

                      【讨论】:

                      • 当然,您不需要依赖 Guava 的 Multimap。它只是让您的生活更轻松,因为您不必重新实现它们、测试它们等。
                      • 这不允许对所有对进行无缝迭代。肯定还有更多的缺点。我正要提出我的解决方案,它也需要额外的一门课,然后看到@Mnementh 的答案就是这样。
                      • 编写基本代码并不总是那么聪明。 Google 更有可能进行更好的测试
                      【解决方案16】:

                      为了完整起见,Apache Commons Collections 也有一个 MultiMap。缺点当然是 Apache Commons 不使用泛型。

                      【讨论】:

                      • 请注意,他们的 MultiMap 实现了 Map 但违反了 Map 方法的约定。这让我很烦。
                      【解决方案17】:

                      如果您想迭代键值对列表(正如您在评论中所写),那么列表或数组应该更好。首先结合你的键和值:

                      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()。我不想硬核维度的数量。
                      • 在这种情况下使用列表。第二个示例更改为 List pairs = new List(); for 循环保持不变。您可以使用以下命令添加一对:pairs.add(pair);
                      • 老实说,这可能是最好的答案。
                      【解决方案18】:

                      您可以简单地为常规 HashMap 中的值传递一个值数组,从而模拟重复键,由您决定使用哪些数据。

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

                      【讨论】:

                      • 谢谢!使用TreeMap&lt;String, ArrayList&lt;MyClass&gt;&gt; 解决了我的重复密钥需求。
                      【解决方案19】:

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

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

                      【讨论】:

                      • 上下文是我认为键是唯一的,但事实证明可能不是。我不想重构所有东西,因为它不会经常使用。
                      • 我想将一个小的 XML 文件转换为类似数据类型的 hashmap。唯一的问题是 XML 文件的结构不固定
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2023-03-15
                      • 1970-01-01
                      • 1970-01-01
                      • 2013-12-24
                      • 2011-02-11
                      • 1970-01-01
                      相关资源
                      最近更新 更多