[TOC]
通常,我们知道某些键的信息,并想要查找与之对应的元素。映射(map)数据结构就是为此设计的。映射用来存放键/值对。如果提供了键,就能够查找到值。

基本映射操作

Java 类库为映射提供了两个通用的实现:HashMapTreeMap。这两个类都实现了 Map 接口。
HahMap 对键进行散列,TreeMap 用键的整体顺序对元素进行排序,并将其组织成搜索树。与 Set 一样,HashMap 稍微快一些,如果不需要按照排列顺序访问键,最好选择 HashMap

  1. Map<String, Employee> staff = new HashMap<>();
  2. Employee jack = new Employee("jack");
  3. Employee kang = new Employee("kang");
  4. Employee rose = new Employee("rose");
  5. staff.put("123456", kang);
  6. staff.put("654321", jack);
  7. staff.put("987654", rose);
  8. String id = "123456";
  9. Employee e = staff.get(id);
  10. System.out.println(e.getName()); // kang
  11. Employee n = staff.getOrDefault("123", new Employee("default"));
  12. System.out.println(n.getName()); // default
  13. staff.forEach((k, v) -> System.out.println("key = " + k + ", value = " + v.getName()));
  14. /*
  15. * key = 123456, value = kang
  16. * key = 654321, value = jack
  17. * key = 987654, value = rose
  18. */

更新 Map 的 value 值

我们想要统计单词的个数,当单词出现的时候,计数器加一。我们可以这样来设计:

  1. Map<String, Integer> counts = new HashMap<>();
  2. counts.put(word, counts.get(word) + 1); // maybe throw NullPonterException

但是,当该单词第一次出现的时候,counts.get(word) 会返回 null,从而导致 NullPointerException 异常
当然,可以使用 getOrDefault()putIfAbsent() 方法来解决这个问题:

  1. counts.put(word, counts.getOrDefault(word, 0));
  2. // or
  3. counts.putIfAbsent(word, 0);
  4. counts.put(word, counts.get(word) + 1);

很明显,库设计者也发现了这个问题,他们用 merge() 来解决这个问题:

  1. counts.merge(word, 1, Integer::sum);

上述先会判断键(word)时候存在,若不存在,将 word 等于 1;否则使用 Integer::sum 函数组合 word 和 1。

Map 视图

Map 有三种视图:

  • Set KeySet(), 需要注意的是,keySet() 不是 HashSetTreeSet,而是实现了 Set 接口的另外某个类的对象。
  • Collection values()
  • Set> entrySet()

下面是使用示例:

  1. Map<String, Employee> staff = new HashMap<>();
  2. ...
  3. Set<String> keys = staff.keySet();
  4. for (String key : keys) System.out.println(key);
  5. for (Map.Entry<String, Employee> entry : staff.entrySet()) { // 注意,这种方法并没有 forEach 好用
  6. String k = entry.getKey();
  7. Employee v = entry.getValue();
  8. System.out.println("key: " + k + ", value: " + v.getName());
  9. }

在上述的视图中,可以调用迭代器的 remove() 来删除对应的键和值。但是不能向视图增加元素,具体原因可以看下一讲。

WeakHashMap

在使用 Map 时,可能有一些键不再正常使用,但是这些键无法从 Map 被垃圾回收。这是因为垃圾回收器跟踪活动对象。只要 Map 对象是活动的,其中的元素也是活动的,他们不能被回收。因此,需要编写程序从长期存活的映射表中删除那些无用的值。或者使用 WeakHashMap 完成这件事情。
WeakHashMap 使用 WeakReference(弱引用)保存键。当某个键已经不被特定对象所引用,就将其回收。如果某个对象只能由 WeakReference 引用,垃圾回收器仍然回收它,同时将引用这个对象的弱引用放入队列中。WeakHashMap 将周期性地检查队列,以便找出新添加的弱引用,WeakHashMap 将删除弱引用对应的条目。

LinkedHashSet 和 LinkedHashMap

LinkedHashSetLinkedHashMap 是用来记住元素的插入顺序的,虽然他们的基本数据没有改变,但是当你插入数据时,会形成一个双向链表,来记录插入元素的顺序。
chapter_VIII-LinkedHashMap

  1. Map<String, String> fruits = new LinkedHashMap<>();
  2. fruits.put("apple", "苹果");
  3. fruits.put("banana", "香蕉");
  4. fruits.put("peach", "桃子");
  5. for (String s : fruits.keySet())
  6. System.out.println(s);
  7. /** print
  8. * apple
  9. * banana
  10. * peach
  11. */

可以指定 LinkedHashSetLinkedHashMap 中的 accessOrdertrue 来达到调用 get()put() ,受影响的条目将从,当前位置删除,并放到链表尾部。当然散列表不会受到影响:

  1. Map<String, String> fruits = new LinkedHashMap<>(16, 0.75f, true);
  2. fruits.put("apple", "苹果");
  3. fruits.put("banana", "香蕉");
  4. fruits.put("peach", "桃子");
  5. fruits.get("apple");
  6. for (String s : fruits.keySet())
  7. System.out.println(s);
  8. /** print
  9. * banana
  10. * peach
  11. * apple // attention
  12. */

注意,accessOrder 是默认访问修饰符(就是没有指定访问修饰符) ,需要使用构造函数 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 来设置 accessOrder 选项

EnumSet 与 EnumMap

由于枚举类型只有有限个实例,所以 EnumSet 内部用位序列实现。如果对应的值在 Set 中,则相应的位被置为 1。
EnumSet 类没有公共的构造器。可以使用静态工厂方法构造这个集:

  1. enum Weekady { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATUDAY, SUNDAY };
  2. EnumSet<Weekady> always = EnumSet.allOf(Weekady.class);
  3. EnumSet<Weekady> never = EnumSet.noneOf(Weekady.class);
  4. EnumSet<Weekady> workday = EnumSet.range(Weekady.MONDAY, Weekady.FRIDAY);
  5. EnumSet<Weekady> mwf = EnumSet.of(Weekady.MONDAY, Weekady.WEDNESDAY, Weekady.FRIDAY);

EnumMap 是一个键类型为枚举类型的 Map。使用时需要在构造器中指定键类型

  1. EnumMap<Weekday, Employee> personInCharge = new Enum<>(Weekday.class);

IdentityHashMap

IdentityHashMap 中,键的散列值不是用 hashCode() 计算的,而是用 System.identityHashCode() 计算的。这是 Object.hashCode() 根据对象的内存地址来计算散列码时所使用的方式。而且,在对两个对象进行比较时,IdentityHashMap 类使用 ==,而不使用 equals