美文网首页
10.7 关于Java的Map的九个问题

10.7 关于Java的Map的九个问题

作者: 明翼 | 来源:发表于2018-10-03 22:30 被阅读15次

通常,Map是由一组键值对组成的数据结构,每个键只能在Map中出现一次。 本文总结了如何使用Java Map及其实现的类的前9个常见问题。 为简单起见,我将在示例中使用泛型。 因此,我将只编写Map而不是特定的Map。 但是你总是可以假设K和V都是可比较的,这意味着K扩展了Comparable而V扩展了Comparable。

0. 转换一个Map为List

在Java中,Map接口提供三个集合视图:键集合、值集合和键值集合。它们都可以转换为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、Map迭代

迭代每对键值是遍历map最基本的操作。在Java中,这样都的键值对存在Map.Entry的映射条目中。Map.entrySet()返回一个键值集合,因此最有效的方法是遍历map的每个条目。

for(Entry entry: map.entrySet()) {
  // get key
  K key = entry.getKey();
  // get value
  V value = entry.getValue();
}

也可以使用迭代器,尤其是在JDK1.5之前。

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的Keys排序

对Map的Key排序是另一个常见的操作。一种方法是将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());
  }
 

另一种方法是使用排序Map,它进一步提供其键的总排序。因此所有的Key必须实现比较器接口或继承comparator。
SortedMap的一个实现类是TreeMap。它的构造函数可以接受比较器。以下代码显示如何将常规的映射转换为有序迎神。

SortedMap sortedMap = new TreeMap(new Comparator() {
 
  @Override
  public int compare(K k1, K k2) {
    return k1.compareTo(k2);
  }
 
});
sortedMap.putAll(map);

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());
  }
 
});

我们仍然可以使用排序的Map来解决这个问题。但是前提是这些值也是唯一的。在这种条件下,你可以将key=value反转为value=key。
这个方案有很强的局限性,因此我并不推荐。

4、初始化静态或不可变的映射

当你希望Map是不可变的时候,将其复制到不可变的map中是一种很好的做法。这种防御性编程技术不仅帮助你创建安全使用,而且还可以安全地创建线程map。

要初始化静态/不可变map,我们可以使用静态初始化器(如下)。这段代码的问题在于,虽然map被声明为static final,但是我们仍然可以在初始化的时候运行它,比如:
Test.map.put(3,"three");,因此它并不是真正的不可改变。要使用静态初始化程序创建不可变Map,我们需要一个额外的匿名类,并在初始化的最后一步将其复制到不可修改的Map中。请参考第二段代码,如果在运行:Test.map.put(3,"three");将会抛出UnsupportedOperationException。

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。最重要的差异包括:

  • 迭代的顺序。 HashMap和Hashtable不保证地图的顺序;特别是,他们不保证订单会随着时间的推移保持不变。但TreeMap将根据键的插入顺序或比较器迭代整个条目。
  • 键值权限。 HashMap允许空键和空值(只允许一个空键,因为不允许两个键相同)。 Hashtable不允许null键或null值。如果TreeMap使用自然排序或其比较器不允许空键,则会抛出异常。
  • 同步。 只有Hashtable是同步的,另外两个则不是。因此,“如果不需要线程安全的实现,建议使用HashMap代替Hashtable。”
    更完整的比较是:
    image.png

6、具有反向查找视图的Map

有时,我们需要一组键 - 键对,这意味着地图的值是唯一的以及键(一对一地图)。该约束使得能够创建地图的“反向查找/视图”。所以我们可以通过其值查找关键字。这种数据结构称为双向映射,但JDK不支持这种映射。

Apache Common Collections和Guava都提供了双向映射的实现,分别称为BidiMap和BiMap。两者都强制要求键和值之间存在1:1的关系。

7、Map的浅拷贝

在java中,大多数Map的实现(如果不是全部的话)都提供了另​​一个Map副本的构造函数。但复制过程不同步。这意味着当一个线程复制一个map时,另一个线程可以在结构上修改它。要[防止意外的不同步Map,应事先使用Collections.synchronizedMap()。

Map copiedMap = Collections.synchronizedMap(map);
另一种有趣的浅层复制方法是使用clone()方法。
然而,Java集合框架的设计者Josh Bloch甚至不推荐它。在谈到“复制构造函数与克隆”时,他说

我经常在具体类上提供公共克隆方法,因为人们期望它。 ...... Cloneable被打破是一种耻辱,但它确实发生了。 ......克隆是一个弱点,我认为人们应该意识到它的局限性。
出于这个原因,我甚至不会告诉你如何使用clone()方法来复制Map.

8、创建一个空的Map

如果Map是不可变的,使用
map = Collections.emptyMap();
否则,如果使用任意实现:
map = new HashMap();

相关文章

网友评论

      本文标题:10.7 关于Java的Map的九个问题

      本文链接:https://www.haomeiwen.com/subject/exdtaftx.html