你的位置:首页 > Java教程

[Java教程]【Simple Java】Java Maps的9个常见问题


一般来说,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 listList keyList = new ArrayList(map.keySet());// value listList valueList = new ArrayList(map.valueSet());// key-value listList 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/