【发布时间】:2015-05-05 13:53:11
【问题描述】:
我有一个 HashMap,我想获得一个新的 HashMap,它只包含第一个 HashMap 中的元素,其中 K 属于特定列表。
我可以查看所有键并填充一个新的 HashMap,但我想知道是否有更有效的方法来做到这一点?
谢谢
【问题讨论】:
我有一个 HashMap,我想获得一个新的 HashMap,它只包含第一个 HashMap 中的元素,其中 K 属于特定列表。
我可以查看所有键并填充一个新的 HashMap,但我想知道是否有更有效的方法来做到这一点?
谢谢
【问题讨论】:
根据您的使用情况,这可能是更有效的实现方式
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) - 这可能是个问题。
复制地图并删除所有不在列表中的键:
Map map2 = new Hashmap(map);
map2.keySet().retainAll(keysToKeep);
【讨论】:
对于 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。
在番石榴的帮助下。
假设您有一个映射 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)。因此,如果您的列表很大,这可能会对性能产生巨大影响。
另一方面,HashSet.contains() 是常数时间 O(1),因此如果有可能使用 Set 而不是 List,这可能是一个不错的方法(注意将 List 转换为Set 无论如何都会花费 O(n),所以最好不要转换:))
【讨论】:
您可以遍历列表并检查 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作为变量名。
new HashMap<> (keys.size(), 1f);,否则将使用 0.75 的负载系数并且地图可能会调整一次大小
ìf (value != null) 不是 containsKey 与 HashMap 的有效替代品。可以写hashMap.put("key", null),然后你的if (value != null) 测试就会被愚弄。 (当然,这是一个如此频繁的设计,以至于在 Map 中设置空值一开始似乎是个坏主意)
您甚至可以自己种植:
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);
}
【讨论】:
是的,有一个解决方案:
Map<K,V> myMap = ...;
List<K> keysToRetain = ...;
myMap.keySet().retainAll(keysToRetain);
Set 上的 retainAll 操作会更新底层映射。见java doc。
编辑
请注意此解决方案修改Map。
【讨论】:
HashMap。
Map<K,V> map2 = new HashMap<>( myMap );。然后拨打map2.retainAll( keysToRetain );
您要求一个新的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 的受限视图。该类将有两个私有最终字段
Map<? extends K, ? extends V> originalMap;
Set<?> restrictedSetOfKeys;
新的Map 的get 方法是这样的
@Override
public V get(Object k) {
if (!restrictedSetOfKeys.contains(k))
return null;
return originalMap.get(k);
}
请注意,restrictedSetOfKeys 最好是 Set 而不是 List,因为如果是 HashSet,get 方法的时间复杂度通常为 O(1)。
【讨论】:
不,因为 HashMap 不维护其条目的顺序。如果您需要某个范围之间的子地图,您可以使用 TreeMap。还有,请看这个question;它似乎与您的路线相似。
【讨论】:
您可以在返回的 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());
}
}
可能有一些语法错误,因为我没有测试过,但是尝试一下。
【讨论】:
如果您有 Map m1 和 List 键,请尝试以下操作
Map m2 = new HashMap(m1);
m2.keySet().retainAll(keys);
【讨论】:
HashMap 复制构造函数所做的。
如果你的键有顺序,你可以使用 TreeMap。
看TreeMap.subMap()
不过,它不允许您使用列表来执行此操作。
【讨论】: