关于Java Maps的9个常见问题
通常,Map是一种数据结构,用来存储键值对的,每个key只能在map中出现1次。这篇文章总结了9个 Map 和其子类很常见的问题。简单起见,例子中我将会使用泛型。因此,我将只考虑常规的map,而不是特定的map。但是你可以假设K和V都可以比较,那意味着K和V都继承了Comparable接口。
1. 把Map转换成List
在Java中,Map提供了3中集合视图:key set,value set,和 key-value set。他们都能使用addAll()转换成为List。下面的代码片段展示了如何从map构建一个ArrayList。
|
|
2.遍历map的记录
对于map来说遍历键值对是很基础的操作,这种键值对在map中被叫做Map.Entry。Map.Entry将会返回键值的集合,因此这是遍历map最有效的方法。
|
|
对于JDK1.5以前的可以这样:
|
|
3. 根据map的key来排序
另一种常见的操作是针对map的key排序。方法1是把Map.Entry 放入list然后使用一个comparator 来进行排序。
|
|
另外一个方法是使用SortedMap,它提供了按照key排序的功能。但是所有的key都必须实现 Comparable或者可以比较操作。
TreeMap是一个SortedMap的实现。它的构造函数可以接受一个比较器。下面的代码显示了如何把一个普通map转换到有序map。
|
|
4. 根据map的值来排序
把map放入list然后排序也可以,但是这次我们比较的是Entry.getValue()。代码几乎和以前一样。
|
|
这个问题我们也可以使用有序map1来解决,但是只有当值是唯一的。在这样的情况下你可以反转key=value 变成 value=key。这个解决方法有很强的局限性,因此我不推荐使用。
5. 初始化一个static的/不可变的Map
当你希望map保持不变,最好的方法是把它复制到一个不可变map中。这样的防御技术会帮助你创建不仅使用安全而且线程安全的map.
为了初始一个static的/不可变的map,我们可以使用static来初始化(如下文代码)。但是即使我们使用了static final来修饰map,我们仍然可以在初始化后操作map,比如:Test.map.put(3,”three”);因此它不是一个实际不可变的。为了创建一个不可变的map使用static初始化话,我们需要一个匿名类,然后将其复制到定义的地方,必须放在初始化的最后(因为不可以修改了嘛)。看第二个代码,如果你调用: Test.map.put(3,”three”);那么会抛出UnsupportedOperationException异常。
|
|
Guava库同样支持不同的方式创建static或者不可变的集合。了解怎么使用。
6. HashMap, TreeMap, 和 Hashtable的不同
在java中Map接口的实现主要有3种HashMap, TreeMap, 和 Hashtable。最主要的区别如下:
- 遍历的顺序。HashMap和Hashtable是不保证遍历的顺序的。主要是在恒定的时间不能保证不变。但是TreeMap 是有序的,它的排序是“自然排序”。
- 键值权限。HashMap可以有null 键和null值,但是 Hashtable不允许有null键或null值。TreeMap 如果是使用自然排序的,那么使用null值会报NullPointerException异常。
- 同步。只有Hashtable 是同步的,别的都不同步。然后如果是线程安全的,我们就不需要同步的,可以使用HashMap 来代替Hashtable 。
一个比较完整的比较:
HashMap | Hashtable | TreeMap | |
---|---|---|---|
遍历有序 | no | no | yes |
null key-value | yes-yes | no-no | no-yes |
synchronized | no | yes | no |
时间性能 | O(1) | O(1) | O(log n) |
实现方式 | buckets | buckets | red-black tree |
{: rules=”groups”}
了解更多HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap
7. Map的反向查看/查找
有后时候我们需要 key-key 的集合。这就意味着map中的值也是和key一样是唯一(one-to-one map)。这时候就可以考虑一下创建“Map反向查看/查找”。所以我们可以根据值找到key。如:双向map,JDK默认是没有支持的。
Apache Common Collections 和 Guava 都提供了双向map的实现。它们分别叫BidiMap 和 BiMap,它们都强制限制了key和value 的1:1 的关系。
8. Map的浅拷贝
在java中很多map的实现提供了一个方式来拷贝map。但是拷贝是不同步的。那就意味着,如果一个线程在拷贝map,另一个线程也可以修改map的数据。为了防止不同步的线程拷贝,一个方式是使用Collections.synchronizedMap()来防止。
|
|
另一种比较有趣的方式是使用clone()方法来进行浅拷贝。但是这个方式不是java集合设计者(Josh Bloch)的推荐方式.Copy constructor versus cloning
,他说:
我常常在具体的类中提供一个公共的方法,因为大家都很喜欢使用。 ……当克隆性不管用的时候这将引起麻烦。而且这情况确实会发生 ……可克隆性是一个微弱的点,我想大家应该意识到它的局限性。
出于这个原因,我就不告诉你如何使用clone()方法复制map了。
9. 创建一个空map
如果Map是不可变的,这样用:
|
|
否则使用构造函数,比如:
|
|