【问题标题】:what is the best way to get a sub HashMap based on a list of Keys?根据键列表获取子 HashMap 的最佳方法是什么?
【发布时间】:2015-05-05 13:53:11
【问题描述】:

我有一个 HashMap,我想获得一个新的 HashMap,它只包含第一个 HashMap 中的元素,其中 K 属于特定列表。

我可以查看所有键并填充一个新的 HashMap,但我想知道是否有更有效的方法来做到这一点?

谢谢

【问题讨论】:

    标签: java hashmap


    【解决方案1】:

    根据您的使用情况,这可能是更有效的实现方式

    public class MapView implements Map{
      List ak;
      Map map;
      public MapView(Map map, List allowableKeys) {
         ak = allowableKeys;
         map = map;
      }
      public Object get(Object key) {
        if (!ak.contains(key)) return null;
        return map.get(key);
      }
    }
    

    【讨论】:

    • 如果使用新地图而不是视图,ak.contains 是 O(n) 与 O(1) - 这可能是个问题。
    • 结果MapView不是HashMap,甚至不是Map。
    【解决方案2】:

    复制地图并删除所有不在列表中的键:

    Map map2 = new Hashmap(map);
    map2.keySet().retainAll(keysToKeep);
    

    【讨论】:

      【解决方案3】:

      对于 Java8 流,有一个功能性(优雅)的解决方案。如果 keys 是要保留的键列表,map 是源 Map

      keys.stream()
          .filter(map::containsKey)
          .collect(Collectors.toMap(Function.identity(), map::get));
      

      完整示例:

          List<Integer> keys = new ArrayList<>();
          keys.add(2);
          keys.add(3);
          keys.add(42); // this key is not in the map
      
          Map<Integer, String> map = new HashMap<>();
          map.put(1, "foo");
          map.put(2, "bar");
          map.put(3, "fizz");
          map.put(4, "buz");
      
          Map<Integer, String> res = keys.stream()
              .filter(map::containsKey)
              .collect(Collectors.toMap(Function.identity(), map::get));
      
          System.out.println(res.toString());
      

      打印:{2=bar, 3=fizz}

      EDIT为地图中不存在的键添加filter

      【讨论】:

      • 这种方法的问题是如果你注释掉map.put(2, "bar");这行你会得到NullPointerException
      【解决方案4】:

      在番石榴的帮助下。

      假设您有一个映射 Map&lt;String, String&gt; 并希望使用来自 List&lt;String&gt; 列表的值进行子映射。

      Map<String, String> map = new HashMap<>();
      map.put("1", "1");
      map.put("2", "2");
      map.put("3", "4");
      
      final List<String> list = Arrays.asList("2", "4");
      
      Map<String, String> subMap = Maps.filterValues(
                      map, Predicates.in(list));
      

      更新/注意:正如评论中提到的@assylias,使用contains() 时,您将有 O(n)。因此,如果您的列表很大,这可能会对性能产生巨大影响。

      另一方面,HashSet.contains() 是常数时间 O(1),因此如果有可能使用 Set 而不是 List,这可能是一个不错的方法(注意将 List 转换为Set 无论如何都会花费 O(n),所以最好不要转换:))

      【讨论】:

      • 与视图相同的评论:如果列表很大,这可能会非常慢。
      • 非常好。更新了我的答案,以提及对大型列表的性能影响。
      • 我很想在这个项目中使用 Guava,但我不能。该解决方案也很有趣,但我想知道是否有没有外部库的解决方案。
      • 问题是关于基于键而不是值的过滤,所以 Maps.filterKeys() 是正确的。
      【解决方案5】:

      您可以遍历列表并检查 HashMap 是否包含映射,而不是查看所有键。然后使用过滤后的条目创建一个新的 HashMap:

      List<String> keys = Arrays.asList('a', 'c', 'e');
      
      Map<String, String> old = new HashMap<>();
      old.put('a', 'aa');
      old.put('b', 'bb');
      old.put('c', 'cc');
      old.put('d', 'dd');
      old.put('e', 'ee');
      
      // only use an inital capacity of keys.size() if you won't add
      // additional entries to the map; anyways it's more of a micro optimization
      Map<String, String> newMap = new HashMap<>(keys.size(), 1f);
      
      for (String key: keys) {
          String value = old.get(key);
          if (value != null) newMap.put(key, value);
      }
      

      【讨论】:

      • 你不能使用new作为变量名。
      • @Bubletan 你是对的,将变量重命名为 newMap。
      • @helpermethod 你应该使用new HashMap&lt;&gt; (keys.size(), 1f);,否则将使用 0.75 的负载系数并且地图可能会调整一次大小
      • ìf (value != null) 不是 containsKey 与 HashMap 的有效替代品。可以写hashMap.put("key", null),然后你的if (value != null) 测试就会被愚弄。 (当然,这是一个如此频繁的设计,以至于在 Map 中设置空值一开始似乎是个坏主意)
      • 在某些情况下,您可能希望在 HashMap 中存储 null 值(您仍然可以验证它是否包含用于区分具有 null 的映射或缺少该特定键的值的键)。 .. 例如,当您使用它来缓存计算机密集型功能的结果时(结果可能为空)...这样就行不通了。
      【解决方案6】:

      您甚至可以自己种植:

      public class FilteredMap<K, V> extends AbstractMap<K, V> implements Map<K, V> {
      
          // The map I wrap.
          private final Map<K, V> map;
          // The filter.
          private final Set<K> filter;
      
          public FilteredMap(Map<K, V> map, Set<K> filter) {
              this.map = map;
              this.filter = filter;
          }
      
          @Override
          public Set<Entry<K, V>> entrySet() {
              // Make a new one to break the bond with the underlying map.
              Set<Entry<K, V>> entries = new HashSet<>(map.entrySet());
              Set<Entry<K, V>> remove = new HashSet<>();
              for (Entry<K, V> entry : entries) {
                  if (!filter.contains(entry.getKey())) {
                      remove.add(entry);
                  }
              }
              entries.removeAll(remove);
              return entries;
          }
      
      }
      
      public void test() {
          Map<String, String> map = new HashMap<>();
          map.put("1", "One");
          map.put("2", "Two");
          map.put("3", "Three");
          Set<String> filter = new HashSet<>();
          filter.add("1");
          filter.add("2");
          Map<String, String> filtered = new FilteredMap<>(map, filter);
          System.out.println(filtered);
      
      }
      

      如果您担心所有的复制,您也可以生成过滤后的Set 和过滤后的Iterator

      public interface Filter<T> {
      
          public boolean accept(T t);
      }
      
      public class FilteredIterator<T> implements Iterator<T> {
      
          // The Iterator
          private final Iterator<T> i;
          // The filter.
          private final Filter<T> filter;
          // The next.
          private T next = null;
      
          public FilteredIterator(Iterator<T> i, Filter<T> filter) {
              this.i = i;
              this.filter = filter;
          }
      
          @Override
          public boolean hasNext() {
              while (next == null && i.hasNext()) {
                  T n = i.next();
                  if (filter.accept(n)) {
                      next = n;
                  }
              }
              return next != null;
          }
      
          @Override
          public T next() {
              T n = next;
              next = null;
              return n;
          }
      }
      
      public class FilteredSet<K> extends AbstractSet<K> implements Set<K> {
      
          // The Set
          private final Set<K> set;
          // The filter.
          private final Filter<K> filter;
      
          public FilteredSet(Set<K> set, Filter<K> filter) {
              this.set = set;
              this.filter = filter;
          }
      
          @Override
          public Iterator<K> iterator() {
              return new FilteredIterator(set.iterator(), filter);
          }
      
          @Override
          public int size() {
              int n = 0;
              Iterator<K> i = iterator();
              while (i.hasNext()) {
                  i.next();
                  n += 1;
              }
              return n;
          }
      
      }
      
      public class FilteredMap<K, V> extends AbstractMap<K, V> implements Map<K, V> {
      
          // The map I wrap.
          private final Map<K, V> map;
          // The filter.
          private final Filter<K> filter;
      
          public FilteredMap(Map<K, V> map, Filter<K> filter) {
              this.map = map;
              this.filter = filter;
          }
      
          @Override
          public Set<Entry<K, V>> entrySet() {
              return new FilteredSet<>(map.entrySet(), new Filter<Entry<K, V>>() {
      
                  @Override
                  public boolean accept(Entry<K, V> t) {
                      return filter.accept(t.getKey());
                  }
      
              });
          }
      
      }
      
      public void test() {
          Map<String, String> map = new HashMap<>();
          map.put("1", "One");
          map.put("2", "Two");
          map.put("3", "Three");
          Set<String> filter = new HashSet<>();
          filter.add("1");
          filter.add("2");
          Map<String, String> filtered = new FilteredMap<>(map, new Filter<String>() {
      
              @Override
              public boolean accept(String t) {
                  return filter.contains(t);
              }
          });
          System.out.println(filtered);
      
      }
      

      【讨论】:

        【解决方案7】:

        是的,有一个解决方案:

        Map<K,V> myMap = ...;
        List<K> keysToRetain = ...;
        myMap.keySet().retainAll(keysToRetain);
        

        Set 上的 retainAll 操作会更新底层映射。见java doc

        编辑 请注意此解决方案修改Map

        【讨论】:

        • 但他要求 HashMap
        • @KrishPrabakar 因此,只需将原始地图传递给第二个地图的构造函数即可复制原始地图:Map&lt;K,V&gt; map2 = new HashMap&lt;&gt;( myMap );。然后拨打map2.retainAll( keysToRetain );
        【解决方案8】:

        您要求一个新的HashMap。由于HashMap 不支持结构共享,因此没有比显而易见的方法更好的方法了。 (我在这里假设null 不能是一个值)。

        Map<K, V> newMap = new HashMap<>();
        for (K k : keys) {
            V v  = map.get(k);
            if (v != null)
                newMap.put(k, v);
        }
        

        如果您不绝对要求创建的新对象是HashMap,您可以创建一个新类(最好是扩展AbstractMap&lt;K, V&gt;)代表原始Map 的受限视图。该类将有两个私有最终字段

        Map<? extends K, ? extends V> originalMap;
        Set<?> restrictedSetOfKeys;
        

        新的Mapget 方法是这样的

        @Override
        public V get(Object k) {
            if (!restrictedSetOfKeys.contains(k))
                return null;
            return originalMap.get(k);
        }
        

        请注意,restrictedSetOfKeys 最好是 Set 而不是 List,因为如果是 HashSetget 方法的时间复杂度通常为 O(1)。

        【讨论】:

          【解决方案9】:

          不,因为 HashMap 不维护其条目的顺序。如果您需要某个范围之间的子地图,您可以使用 TreeMap。还有,请看这个question;它似乎与您的路线相似。

          【讨论】:

          • 我对特定范围不感兴趣:只是键属于指定列表。
          【解决方案10】:

          您可以在返回的 K HashMap 上使用 clone() 方法。

          类似这样的:

          import java.util.HashMap;
          
          public class MyClone {    
               public static void main(String a[]) {    
                  Map<String, HashMap<String, String>> hashMap = new HashMap<String, HashMap<String, String>>();    
                  Map hashMapCloned = new HashMap<String, String>();    
          
                  Map<String, String> insert = new HashMap<String, String>();
          
                  insert.put("foo", "bar");
                  hashMap.put("first", insert);
          
                  hashMapCloned.put((HashMap<String, String>) hashMap.get("first").clone());
          
              }    
          }
          

          可能有一些语法错误,因为我没有测试过,但是尝试一下。

          【讨论】:

          • 更喜欢使用超类型/接口来声明变量而不是具体类。
          【解决方案11】:

          如果您有 Map m1 和 List 键,请尝试以下操作

          Map m2 = new HashMap(m1);
          m2.keySet().retainAll(keys);
          

          【讨论】:

          • @tkruse 这就是使用HashMap 复制构造函数所做的。
          【解决方案12】:

          如果你的键有顺序,你可以使用 TreeMap。

          TreeMap.subMap()

          不过,它不允许您使用列表来执行此操作。

          【讨论】:

          • @OP 还指定了一个 HashMap。你说的是 TreeMap
          猜你喜欢
          • 1970-01-01
          • 2018-06-26
          • 1970-01-01
          • 2011-10-08
          • 1970-01-01
          • 2015-05-10
          • 2013-08-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多