通常来说,Map是一个由键值对组成的数据结构,并且在一个 map 中,每一个 key 都只能出现一次.这篇文章总结了九个关于如何使用 Java Map 和它的实现类的问题.后文中为了方便,Map的类型都使用的是泛型.因此,这里仅会使用Map而不是指定数据类型的 Map,但是你仍然可以假设K 和V 都是可排序的.
0.Convert a Map to a List
在 Java 中Map 接口提供了三种 collection views:key set,value set,和 key-value set.这些都可以通过使用构造方法或者allAll()被转换成List.下面这个代码段展示了如何从一个 map 中构造一个ArrayList
// key list
List keyList = new ArrayList(map.keySet());
// value list
List valueList = new ArrayList(map.values());
// key-value list
List entryList = new ArrayList(map.entrySet());
1.Iterate over a Map
迭代访问键值对是一种常见的遍历 map 的方式.在 Java 中,这样的键值对是存在 mapp 的Map.Entry 内部类中.这个内部类返回一个 key-value set,因此遍历一个 map 最有效的方式是
for(Entry entry: map.entrySet()) {
// get key
K key = entry.getKey();
// get value
V value = entry.getValue();
}
同样可以使用迭代器(Iterator)
Iterator itr = map.entrySet().iterator();
while(itr.hasNext()) {
Entry entry = itr.next();
// get key
K key = entry.getKey();
// get value
V value = entry.getValue();
}
2.Sort a Map on the keys
如何给map 的 key 进行排序也是一个被频繁提起的问题.一个有效的方式是将 Map.Entry 放在 list 之中,然后用 comparaotr 接口对 list 进行排序
List list = new ArrayList(map.entrySet());
Collections.sort(list, new Comparator() {
@Override
public int compare(Entry e1, Entry e2) {
return e1.getKey().compareTo(e2.getKey());
}
});
另外一个方式是使用SortedMap.如果要使用SortMap 就得要保证所有的 key 都得实现Comparable 接口,或者接收一个Comparator.
SortMap 的一个实现类是TreeMap.它的构造方法可以接收一个Comparator.下面这段代码展示了如何将一个普通的 Map 转成一个可排序的 Map.
SortedMap sortedMap = new TreeMap(new Comparator() {
@Override
public int compare(K k1, K k2) {
return k1.compareTo(k2);
}
});
sortedMap.putAll(map);
3.Sort a Map on the values
将map 放在list 中排序同样也适用于这种情况,不过这次调用的是Entry.getValue()方法,来看一下下面这段代码:
List list = new ArrayList(map.entrySet());
Collections.sort(list, new Comparator() {
@Override
public int compare(Entry e1, Entry e2) {
return e1.getValue().compareTo(e2.getValue());
}
});
同样的,仍然可以使用一个 sorted map 来解决这个问题,不过这仅仅适用于所有的 value 也都是唯一的情况.在这种情况下,你可以将键值对倒转使用,就像 key=value 和 value=key.但是这种情形具有太强的限制性,所以这里并不推荐使用
4.Initialize a static/immutable Map
如果你希望你的 map 在代码中保持不变,将它变成一个 immutable map 是一个很不错的方法.这种防御性的代码技术将为你创造一个线程安全并且不会改变的 map.
我们可以通过使用 static 代码块来创建一个 static/immutable map,就像下面这样
public class Test {
private static final Map map;
static {
map = new HashMap();
map.put(1, "one");
map.put(2, "two");
}
}
这段代码的问题是即使 map 被声明成了一个静态常量,仍然可以通过Test.map.put
这种方式对 map 进行修改.因此这并不是真正的 immutable.为了能创建一个真的不可变的 map,我们需要一个额外的匿名类,并且将这个匿名类复制到一个不可修改的 map 中,就像下面这段代码一样
public class Test {
private static final Map map;
static {
Map aMap = new HashMap();
aMap.put(1, "one");
aMap.put(2, "two");
map = Collections.unmodifiableMap(aMap);
}
}
如果对这段代码调用Test.map.put
方法,将会抛出一个UnsupportedOperationException 异常.
Guava 库提供了几种不同的方式来创建 static/immutable collection.这里就不展开讲解,如果感兴趣,可以查看他们的git.
5.Difference between HashMap,TreeMap,and Hashtable
Java 中的Map 有三个主要的实现类,它们分别是:HashMap,TreeMap,和Hashtable.下面列举一下这几个实现类最主要的区别:
1.迭代的顺序.HashMap和Hashtable是不保证取值的顺序就是存值的顺序的,甚至无法保证每次遍历时的顺序(即分两次遍历,返回顺序也可能不同).但是TreeMap的迭代顺序是依据 key 的自然排序或者是给 key 一个Comparator.
2.key-value权限.HashMap允许null key和 null value,Hashtable即不允许null key也不允许null value.而TreeMap则是分情况的,如果它使用的是自然排序或者它的comparator不允许null key,则使用空值会抛出异常.
3.Synchronized.只有Hashtable是同步的,其他的都不是.因此,如果不需要线程安全的话,推荐使用HashMap.
如果想了解更多,可以查看这篇文章.
6.A Map with reverse view/lookup
有些时候,我们需要一个key-key的set,这意味着 map 的 value 就像 key 一样,是不重复的.这种约束创造了一种可反转的 map.所以我们可以通过 value 来找 key.这种数据结构被称为双向映射(bidirectional map),但是不幸的是,JDK 并不支持这种 map.
不过 Apache 和 Guava 提供的工具类都有这种双向映射的实现,分别是BidiMap和BiMap.它们都强制约束了 key 与 value 之间一对一的关系
7.Shallow copy of a Map
Java 提供的大多数 Map 的实现类都提供了一个复制其他map的构造方法.但是这个复制的过程不是线程安全的.这意味着当一个线程在复制一个 map,另外一个可能在修改它的结构.为了避免这种不同步的意外发生,map 需要提前使用Collections.synchronizedMap方法
Map copiedMap = Collections.synchronizedMap(map);
另外一个浅拷贝的方法是使用clone().然鹅甚至 Java 集合框架的设计者都不建议使用这种方式
I often provide a public clone method on concrete classes because people expect it. ... It's a shame that Cloneable is broken, but it happens. ... Cloneable is a weak spot, and I think people should be aware of its limitations.
正是由于这个原因,这里就不讲如何通过clone()方法来实现map 的浅复制了.
8.Create an empty Map
如果要创建一个 immutable map,可以使用
map = Collections.emptyMap();
否则,可以使用任一一个实现类,比如
map = new HashMap();