一般来说,Map是一种由键值对组成的数据结构,其中键(key)在Map中是不能重复的;
本篇文章总结了9个最常见的问题(关于Java Map及其实现类);
出于简单考虑,在代码例子中我将不使用泛型,因此,我将仅仅写上Map而不是写上Map<K, V>,且Map中的Key和Value都是可以比较的,意味着K和V都实现了Comparable接口;
1.Map转List
在Java中,Map接口提供了三个集合视图,即key set, value set,和key-value set。它们都可以转成List集合,如下代码所示:
// key list List keyList = new ArrayList(map.keySet()); // value list List valueList = new ArrayList(map.valueSet()); // key-value list List entryList = new ArrayList(map.entrySet());
2.迭代遍历Map中的key-value
迭代遍历Map中的键值对是最基本的操作。在Java中,键值对信息存储在Map.Entry类中。通过调用Map.entrySet()方法会返回键值对集合,因此最有效的遍历Map中的键值对如下代码所示:
for(Entry entry: map.entrySet()) { // get key K key = entry.getKey(); // get value V value = entry.getValue(); }
当然,也可以通过迭代器遍历,如下代码:
while(itr.hasNext()) { Entry entry = itr.next(); // get key K key = entry.getKey(); // get value V value = entry.getValue(); }
3.Map中的元素按Key排序
对Map中的元素按key排序是另一个经常使用的操作。有一种方法是将Map中的元素放进List集合,然后通过一个比较器对集合进行排序,如下代码示例:
List list = new ArrayList(map.entrySet()); Collections.sort(list, new Comparator() { public int compare(Entry e1, Entry e2) { return e1.getKey().compareTo(e2.getKey()); } }); }
另一种方法是使用SortedMap,其提供了对Key进行排序的功能;前提是Map中的Key需要实现Comparable接口或者通过构造方法传入比较器;
TreeMap是SortedMap的一个实现类,它的构造方法能够接收一个比较器,以下代码展示了如何将普通Map转成SortedMap:
SortedMap sortedMap = new TreeMap(new Comparator() { @Override public int compare(K k1, K k2) { return k1.compareTo(k2); } }); sortedMap.putAll(map);
4.Map中的元素按Value排序
同样可以参考前面的将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()); } });
对于这个问题,当map中的value都是唯一的情况,我们仍然可以使用sorted map来排序,在这种情况下,你可以将key赋值给value,value赋值给key来实现该功能;但是这种解决方法十分受限,非常不建议使用;
5.初始化一个不可修改的Map
当你想要使用Map来存储一些常量时,将他们存储到一个不可修改的Map是一个好习惯。这种防御性编程技术会帮助你创建一个能安全使用且线程安全的map;
我们可以使用静态代码块来初始化一个静态不可变的map,如下代码:
public class Test { private static final Map map; static { map = new HashMap(); map.put(1, "one"); map.put(2, "two"); } }
然而以上代码是有问题的,即使map被声明成static final,我们仍然可以操作该map,如Test.map.put(3,"three");因此,这不是不可变的map;
为了创建一个不可变的map,我们需要一个额外的map对象,将它作为入参传入Collections.unmodifiableMap方法中,然后得到一个不可变的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(3,"three")将抛出UnsupportedOperationException异常;
Google的Guava库支持多种方式创建不可变的Map,想学习的可以参考其官方使用手册;
6.HashMap, TreeMap, 和Hashtable的区别
HashMap, TreeMap, 和Hashtable是Map接口的三个主要实现类,它们之间的主要区别如下:
- 是否有序。HashMap和HashTable不会保证元素的有序性,随着时间推移,元素的顺序可能会发生改变;然而TreeMap是有序的,会根据自然顺序或依据比较器排序;
- key- value限制。HashMap允许key和value为null(key只能有一个为null,因为key不能重复)。HashTable不允许key 或value为null;TreeMap如果使用自然顺序或者比较器不允许key为null,使用null key将抛出异常;另外,在TreeMap中value是可以为null的;
- 是否同步。只有Hashtable是同步的,其它的不是。如果线程安全不在考虑范围的话,强烈建议使用HashTable。
另一个比较完整的比较如下:
它们之间的更多比较信息可参考如下链接:
http://www.programcreek.com/2013/03/hashmap-vs-treemap-vs-hashtable-vs-linkedhashmap/
7.反向查找Map
有时候,我们需要key和value都唯一,这种情况允许我们创建一个双向查找Map,也就是说,可以通过唯一的value去查找唯一的Key,遗憾的是,JDK并不支持该数据结构;
Apache Common Collections 和 Guava 提供了对双向查找Map的支持,被叫做BidiMap和BiMap,它们规定了key和value必须是1对1的关系。
8.Map浅拷贝
大部分Map实现类都提供了构造方法,用于对另外一个Map的复制,但是这个复制操作不是同步的,意味着一个线程在对Map进行复制操作的时候,另一个线程可能会对它结构性的修改(增删该等操作);为了阻止意外的非同步操作,应该事先使用Collections.synchronizedMap()方法去创建一个同步Map,如下:
Map copiedMap = Collections.synchronizedMap(map);
另外一种浅拷贝方法是使用clone()方法,然而它并不被集合框架作者Josh Bloch推荐;他曾在一次访谈中谈到关于”拷贝构造方法VS克隆方法“,他说:
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.
9.创建一个空Map
如果是不可变Map,使用Collections工具类创建,如下:
map = Collections.emptyMap();
否则,直接new,如下:
map = new HashMap();
译文链接:http://www.programcreek.com/2013/09/top-9-questions-for-java-map/