繁体   English   中英

根据键列表获取子 HashMap 的最佳方法是什么?

[英]what is the best way to get a sub HashMap based on a list of Keys?

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

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

谢谢

使用 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}

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

是的,有一个解决方案:

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

Set上的retainAll操作更新底层映射。 请参阅Java 文档

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

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

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

在番石榴的帮助下。

假设您有一个地图Map<String, String>并且想要使用List<String>列表中的值进行子映射。

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 contains() 因此,如果您的列表很大,这可能会对性能产生巨大影响。

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

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

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);
  }
}

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

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

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

看看TreeMap.subMap()

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

您可以遍历列表并检查 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);
}

你甚至可以自己种植:

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);

}

您可以在返回的 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());

    }    
}

它可能有一些语法错误,因为我还没有测试过,但请尝试类似的方法。

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

你要求一个新的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<K, V> )表示原始Map的受限视图。 该类将有两个私有 final 字段

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会更好,因为如果它是一个HashSet您的get方法的时间复杂度通常为 O(1)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM