[TOC]
通常,我们知道某些键的信息,并想要查找与之对应的元素。映射(map)数据结构就是为此设计的。映射用来存放键/值对。如果提供了键,就能够查找到值。
基本映射操作
Java 类库为映射提供了两个通用的实现:HashMap 和 TreeMap。这两个类都实现了 Map 接口。HahMap 对键进行散列,TreeMap 用键的整体顺序对元素进行排序,并将其组织成搜索树。与 Set 一样,HashMap 稍微快一些,如果不需要按照排列顺序访问键,最好选择 HashMap。
Map<String, Employee> staff = new HashMap<>();Employee jack = new Employee("jack");Employee kang = new Employee("kang");Employee rose = new Employee("rose");staff.put("123456", kang);staff.put("654321", jack);staff.put("987654", rose);String id = "123456";Employee e = staff.get(id);System.out.println(e.getName()); // kangEmployee n = staff.getOrDefault("123", new Employee("default"));System.out.println(n.getName()); // defaultstaff.forEach((k, v) -> System.out.println("key = " + k + ", value = " + v.getName()));/** key = 123456, value = kang* key = 654321, value = jack* key = 987654, value = rose*/
更新 Map 的 value 值
我们想要统计单词的个数,当单词出现的时候,计数器加一。我们可以这样来设计:
Map<String, Integer> counts = new HashMap<>();counts.put(word, counts.get(word) + 1); // maybe throw NullPonterException
但是,当该单词第一次出现的时候,counts.get(word) 会返回 null,从而导致 NullPointerException 异常
当然,可以使用 getOrDefault() 或 putIfAbsent() 方法来解决这个问题:
counts.put(word, counts.getOrDefault(word, 0));// orcounts.putIfAbsent(word, 0);counts.put(word, counts.get(word) + 1);
很明显,库设计者也发现了这个问题,他们用 merge() 来解决这个问题:
counts.merge(word, 1, Integer::sum);
上述先会判断键(word)时候存在,若不存在,将 word 等于 1;否则使用 Integer::sum 函数组合 word 和 1。
Map 视图
Map 有三种视图:
- Set
KeySet(), 需要注意的是, keySet()不是HashSet或TreeSet,而是实现了Set接口的另外某个类的对象。 - Collection
values() - Set
> entrySet()
下面是使用示例:
Map<String, Employee> staff = new HashMap<>();...Set<String> keys = staff.keySet();for (String key : keys) System.out.println(key);for (Map.Entry<String, Employee> entry : staff.entrySet()) { // 注意,这种方法并没有 forEach 好用String k = entry.getKey();Employee v = entry.getValue();System.out.println("key: " + k + ", value: " + v.getName());}
在上述的视图中,可以调用迭代器的 remove() 来删除对应的键和值。但是不能向视图增加元素,具体原因可以看下一讲。
WeakHashMap
在使用 Map 时,可能有一些键不再正常使用,但是这些键无法从 Map 被垃圾回收。这是因为垃圾回收器跟踪活动对象。只要 Map 对象是活动的,其中的元素也是活动的,他们不能被回收。因此,需要编写程序从长期存活的映射表中删除那些无用的值。或者使用 WeakHashMap 完成这件事情。WeakHashMap 使用 WeakReference(弱引用)保存键。当某个键已经不被特定对象所引用,就将其回收。如果某个对象只能由 WeakReference 引用,垃圾回收器仍然回收它,同时将引用这个对象的弱引用放入队列中。WeakHashMap 将周期性地检查队列,以便找出新添加的弱引用,WeakHashMap 将删除弱引用对应的条目。
LinkedHashSet 和 LinkedHashMap
LinkedHashSet 和 LinkedHashMap 是用来记住元素的插入顺序的,虽然他们的基本数据没有改变,但是当你插入数据时,会形成一个双向链表,来记录插入元素的顺序。
Map<String, String> fruits = new LinkedHashMap<>();fruits.put("apple", "苹果");fruits.put("banana", "香蕉");fruits.put("peach", "桃子");for (String s : fruits.keySet())System.out.println(s);* apple* banana* peach*/
可以指定 LinkedHashSet 或 LinkedHashMap 中的 accessOrder 为 true 来达到调用 get() 或 put() ,受影响的条目将从,当前位置删除,并放到链表尾部。当然散列表不会受到影响:
Map<String, String> fruits = new LinkedHashMap<>(16, 0.75f, true);fruits.put("apple", "苹果");fruits.put("banana", "香蕉");fruits.put("peach", "桃子");fruits.get("apple");for (String s : fruits.keySet())System.out.println(s);* banana* peach* apple // attention*/
注意,
accessOrder是默认访问修饰符(就是没有指定访问修饰符) ,需要使用构造函数public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)来设置accessOrder选项
EnumSet 与 EnumMap
由于枚举类型只有有限个实例,所以 EnumSet 内部用位序列实现。如果对应的值在 Set 中,则相应的位被置为 1。EnumSet 类没有公共的构造器。可以使用静态工厂方法构造这个集:
enum Weekady { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATUDAY, SUNDAY };EnumSet<Weekady> always = EnumSet.allOf(Weekady.class);EnumSet<Weekady> never = EnumSet.noneOf(Weekady.class);EnumSet<Weekady> workday = EnumSet.range(Weekady.MONDAY, Weekady.FRIDAY);EnumSet<Weekady> mwf = EnumSet.of(Weekady.MONDAY, Weekady.WEDNESDAY, Weekady.FRIDAY);
EnumMap 是一个键类型为枚举类型的 Map。使用时需要在构造器中指定键类型:
EnumMap<Weekday, Employee> personInCharge = new Enum<>(Weekday.class);
IdentityHashMap
在 IdentityHashMap 中,键的散列值不是用 hashCode() 计算的,而是用 System.identityHashCode() 计算的。这是 Object.hashCode() 根据对象的内存地址来计算散列码时所使用的方式。而且,在对两个对象进行比较时,IdentityHashMap 类使用 ==,而不使用 equals。
