Java Map常用的几种用法。
通常,Map是由一组键值对组成的数据结构,每个键只能在映射中出现一次。这篇文章总结了有关如何使用Java Map及其实现的类的前9个常见问题解答。为了简单起见,我将在示例中使用泛型。因此,我将只写Map而不是具体的Map。但是,您始终可以假设K和V都是可比较的,这意味着K extends Comparable
和V extends Comparable
。
0.将Map转换为列表
在Java中,Map
接口提供了三个集合视图:键集,值集和键值集。可以使用构造函数或addAll()
方法将它们全部转换为List
。以下代码段显示了如何从地图构造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.遍历Map
遍历每对键值是遍历地图的最基本操作。在Java中,这样的对存储在名为Map.Entry
的映射条目中。Map.entrySet()
返回键值集,因此遍历Map
每个条目的最有效方法是
for(Entry entry: map.entrySet()) {
// get key
K key = entry.getKey();
// get value
V value = entry.getValue();
}
也可以使用Iterator
,尤其是在JDK 1.5之前
for(Entry entry: map.entrySet()) {
// get key
K key = entry.getKey();
// get value
V value = entry.getValue();
}
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.以Map的key排序
在Key
在Map
进行排序是另一种常见的操作。一种方法是将Map.Entry
放入列表中,并使用对值进行排序的比较器对其进行排序。
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
,它进一步提供了其键的总排序。因此,所有密钥必须实现Comparable
或被比较器接受。
一个实现类的SortedMap
的是TreeMap
的。它的构造函数可以接受比较器。以下代码显示了如何将普通Map转换为排序Map。
SortedMap sortedMap = new TreeMap(new Comparator() {
@Override
public int compare(K k1, K k2) {
return k1.compareTo(k2);
}
});
sortedMap.putAll(map);
3.根据values对Map排序
将Map
放入列表并对其进行排序也适用于这种情况,但是我们需要比较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());
}
});
我们仍然可以对此问题使用排序映射,但前提是值也必须唯一。在这种情况下,您可以将key = value
对反转为value = key
。这个解决方案有很强的局限性,因此我不推荐这样做。
初始化静态/不可变Map
当您希望Map
保持不变时,将其复制到不可变Map
中是一种好习惯。这种防御性编程技术不仅可以帮助您创建安全使用的线程,而且还可以创建线程安全的Map
。
要初始化静态/不可变Map
,我们可以使用静态初始化器(如下所示)。这段代码的问题在于,尽管Map
被声明为static final
,但我们仍然可以在初始化后像上操作它Test.map.put(3,"three")``;。因此,它并不是真正不变的。要使用静态初始化程序创建不可变的
Map,我们需要一个额外的匿名类,并在初始化的最后一步将其复制到不可修改的
Map中。请参阅第二段代码。然后,如果运行,将抛出
UnsupportedOperationExceptionTest.map.put(3,"three");`。
public class Test {
private static final Map map;
static {
map = new HashMap();
map.put(1, "one");
map.put(2, "two");
}
}
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);
}
}
Guava
库还支持引入静态和不可变集合的不同方法。要了解有关Guava
不可变集合实用程序的好处的更多信息,请参见《Guava用户指南》中介绍的不可变集合。
5. HashMap,TreeMap和Hashtable之间的区别
Java中有三个主要的Map
接口实现:HashMap
,TreeMap
和Hashtable
。最重要的区别包括:
1.迭代的顺序。
HashMap
和Hashtable
不保证Map
的顺序;特别是,它们不能保证顺序会随着时间的推移保持恒定。但是TreeMap
将根据键的“自然顺序”或由比较器迭代整个条目。2.键值取值。
HashMap
允许使用null
键和null
值(仅允许使用一个null
键,因为不允许两个键相同)。哈希表不允许使用空键或空值。如果TreeMa
p使用自然顺序或其比较器不允许使用null
键,则将引发异常。3.线程安全。 仅
Hashtable
是线程安全的,其它的都不是。因此,“如果不需要线程安全的实现,建议使用HashMap代替Hashtable。”
比较完整的比较是
HashMap | 哈希表 | 树状图 | |
---|---|---|---|
迭代顺序 | 没有 | 没有 | 是 |
空键值 | yes-yes | no-no | no-yes |
线程安全 | 没有 | 是的 | 没有 |
时间复杂度 | O(1) | O(1) | O(log n) |
实现方式 | 水桶 | 水桶 | 红黑树 |
6.具有反向查找的Map,value找key
有时,我们需要一组键-键对,这意味着映射的值和键(一对一映射)都是唯一的。该约束使得能够创建Map
的“反向查找”。因此,我们可以通过键的值查找键。这种数据结构称为双向映射,不幸的是JDK不支持。
Apache Common Collections
和Guava
都提供了双向地图的实现,分别称为BidiMap
和BiMap
。两者都强制要求键和值之间存在1:1关系的限制。
7.Map的浅表副本
Java的大多数映射实现(如果不是全部)都提供了另一个映射副本的构造函数。但是复制过程不同步。这意味着当一个线程复制Map
时,另一线程可以在结构上进行修改。为了[防止意外的非同步副本,应该使用Collections
。提前进行syncedMap()
。
Map copiedMap = Collections.synchronizedMap(map);
浅表复制的另一种有趣方式是使用clone()
方法。但是,Java收集框架的设计者Josh Bloch甚至不建议这样做。
我经常在具体的类上提供公共克隆方法,因为人们期望这样做。...可克隆性被破坏很遗憾,但是它确实发生了。...可克隆是一个薄弱环节,我认为人们应该意识到它的局限性。
因此,我什至不会告诉您如何使用clone()
方法复制地图。
8.创建一个空的Map
如果Map
是不可变的,请使用
map = Collections.emptyMap();
否则,请使用任何实现。例如
map = new HashMap ();