[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()); // kang
Employee n = staff.getOrDefault("123", new Employee("default"));
System.out.println(n.getName()); // default
staff.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));
// or
counts.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
。