简介
Map是一个键映射到值的对象。 一个Map不能包含任何的重复的键,也就是说每个键最多映射到一个值。他模拟了数学概念中的映射。Map 接口中包括了基本的操作(put,get,remove etc)和 多元操作 (putall and clear等)还有 集合视图(keyset 等)
Java 平台上包含了三种主要的Map 接口的实现。1. Hashmap 2. TreeMap 3. LinkedHashMap。 他们的特征和性能正是和Hashset, Treeset, LinkedHashSet 类似。
以下的内容让我们来看看Map 接口的细节。但是首先,这里有几个关于JDK 8 Map操作的例子。模拟真实世界的对象是面向对象编程的一个常见的任务,所以让我们来找一个合理的真实世界的例子。 想象下在某个部门有一组员工。
// Group employees by department Map<Department, List<Employee>> byDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment));
或者计算某个部门所有员工的工资总和
// Compute sum of salaries by department Map<Department, Integer> totalByDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment, Collectors.summingInt(Employee::getSalary)));
我们还可以通过city分类people
// Classify Person objects by city Map<String, List<Person>> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity));
或者更复杂点的map
// Cascade Collectors Map<String, Map<String, List<Person>>> peopleByStateAndCity = personStream.collect(Collectors.groupingBy(Person::getState, Collectors.groupingBy(Person::getCity)))
(这里是我自己的: 在java 8中新引用的stream(), 给我门的编程提供了很多的便利,stream 简单来说就是可以讲collection<T>转化成一个T的stream然后对这个stream应用一些方法 例如filter or map etc详情请见 Stream API)
Map 接口的基本操作
put
,
get
,
containsKey
,
containsValue
,
size
, and
isEmpty
) 他们的表现是和hashtable中是一样的。以下这段代码显示的是找到输入list中各个单词出现的频数。
import java.util.*; public class Freq { public static void main(String[] args) { Map<String, Integer> m = new HashMap<String, Integer>(); // Initialize frequency table from command line for (String a : args) { Integer freq = m.get(a); m.put(a, (freq == null) ? 1 : freq + 1); } System.out.println(m.size() + " distinct words:"); System.out.println(m); } }
以上代码只有一点稍微有点意思就是 map的put操作。他的参数是一个条件表达式,因为他会先判断这个被操作的单词是否已经在map中,如果在的话就讲当前的频数加1.
if it is to be it is up to me to delegate那么输入将会是
8 distinct words: {to=3, delegate=1, be=1, it=2, up=1, if=1, me=1, is=2}
8 distinct words: {be=1, delegate=1, if=1, is=2, it=2, me=1, to=3, up=1}
但是如果你想要得到的结果是按照输入的顺序的话(单词第一次出现位置的顺序),那么则需要把HashMap换成LinkedHashMap
8 distinct words: {if=1, it=2, is=2, to=3, be=1, up=1, me=1, delegate=1}
像Set和List接口一样, Map加强了equals和hashcode这俩方法,有了这俩方法我门就能逻辑上比较两个map从而可以忽略他们的实现类型简单来说就是如果两个map的键值对是一样的那么这两个map就是一样的。
Map<K, V> copy = new HashMap<K, V>(m);
Map 接口的Bulk操作
clear这个操作的作用和他的字面意思是一样的。它将删掉map上所有的mapping关系。putall操作和collection接口的addall操作很像。而且,很明显的用途是将一个map完全放入另一个map中。他还有另一个更灵活的用法。假设有一个map被用来表示一个attribute-value对的集合;putall操作加上map的构造函数一起就能提供一个干净利索的方法来实现实例化并将default的赋给他自己。
(自己的理解: 他这一大段就说了一个事,想要把两个map给放到一起我们可以使用putall方法, 值得注意的是如果两个map有相同key的话下面代码的override的那个map会占主导地位)
static <K, V> Map<K, V> newAttributeMap(Map<K, V>defaults, Map<K, V> overrides) { Map<K, V> result = new HashMap<K, V>(defaults); result.putAll(overrides); return result; }
集合视图
for (KeyType key : m.keySet()) System.out.println(key);
// Filter a map based on some // property of its keys. for (Iterator<Type> it = m.keySet().iterator(); it.hasNext(); ) if (it.next().isBogus()) it.remove();
迭代map的值的方法和这个方法很类似,如下
for (Map.Entry<KeyType, ValType> e : m.entrySet()) System.out.println(e.getKey() + ": " + e.getValue());
很多人都会担心这种方法来迭代一个map可能很慢,因为map必须创建一个新的Collection 对象当每次Collection 视图的操作被调用。
调用 Iterator 的remove 方法再配合使用collection view的三种方式,我们能对map中的元素进行删除操作。 这个就是前面的Iterator的例子
通过entrySet视图,给了我们一种能改变某个键的值的可能性。Map.entry的setvalue方法在迭代中可以对entry的值进行更改。注意这是唯一的一种安全的在迭代中改变map的值的方法。如果底层map在迭代中被另一种方式修改那么这个行为将是未指定的。
集合视图支持多种元素删除的方式 - remove, removeall, retainAll(保持输入set删掉剩余其他), 和 clear. 和 Iterator.Remove的操作。
集合视图在某些情况下不支持元素的添加。因为他不是很符合常理当我们使用KeySet 和 values view的时候进行添加操作。同理对于entryset也是一样的。因为map的本身就提供了put 和 putAll的操作。
(自己的观点:以上这一小节说的挺绕的,但是其实就是说Collection view例如 keyset values entryset等等在某些情况下有某些作用,并且把他们和Map这个概念分开来讲。我是这样理解的。)
Collection 视图的一些很厉害的用法
if (m1.entrySet().containsAll(m2.entrySet())) { ... }
相似的,如果你想要知道是否两个map的key是否是一样的那么可以使用
if (m1.keySet().equals(m2.keySet())) { ... }
static <K, V> boolean validate(Map<K, V> attrMap, Set<K> requiredAttrs, Set<K>permittedAttrs) { boolean valid = true; Set<K> attrs = attrMap.keySet(); if (! attrs.containsAll(requiredAttrs)) { Set<K> missing = new HashSet<K>(requiredAttrs); missing.removeAll(attrs); System.out.println("Missing attributes: " + missing); valid = false; } if (! permittedAttrs.containsAll(attrs)) { Set<K> illegal = new HashSet<K>(attrs); illegal.removeAll(permittedAttrs); System.out.println("Illegal attributes: " + illegal); valid = false; } return valid; }
Set<KeyType>commonKeys = new HashSet<KeyType>(m1.keySet()); commonKeys.retainAll(m2.keySet());
m1.entrySet().removeAll(m2.entrySet());
假设你想要从map1中移除所有map2中有的键,你这样这样
m1.keySet().removeAll(m2.keySet());
Set<Employee> individualContributors = new HashSet<Employee>(managers.keySet()); individualContributors.removeAll(managers.values());
Employee simon = ... ; managers.values().removeAll(Collections.singleton(simon));
当 你做完下面这些操作之后,你可能就会发现有哪些员工的manager已经不在为这个公司工作了,最大的manager按照给他自己汇报的原则来算。(这个例子还挺好玩大家好好感受感受)
Map<Employee, Employee> m = new HashMap<Employee, Employee>(managers); m.values().removeAll(managers.keySet()); Set<Employee> slackers = m.keySet();
Multimaps 多重map
import java.util.*; import java.io.*; public class Anagrams { public static void main(String[] args) { int minGroupSize = Integer.parseInt(args[1]); // Read words from file and put into a simulated multimap Map<String, List<String>> m = new HashMap<String, List<String>>(); try { Scanner s = new Scanner(new File(args[0])); while (s.hasNext()) { String word = s.next(); String alpha = alphabetize(word); List<String> l = m.get(alpha); if (l == null) m.put(alpha, l=new ArrayList<String>()); l.add(word); } } catch (IOException e) { System.err.println(e); System.exit(1); } // Print all permutation groups above size threshold for (List<String> l : m.values()) if (l.size() >= minGroupSize) System.out.println(l.size() + ": " + l); } private static String alphabetize(String s) { char[] a = s.toCharArray(); Arrays.sort(a); return new String(a); } }
如下是输出的结果
9: [estrin, inerts, insert, inters, niters, nitres, sinter, triens, trines] 8: [lapse, leaps, pales, peals, pleas, salep, sepal, spale] 8: [aspers, parses, passer, prases, repass, spares, sparse, spears] 10: [least, setal, slate, stale, steal, stela, taels, tales, teals, tesla] 8: [enters, nester, renest, rentes, resent, tenser, ternes, treens] 8: [arles, earls, lares, laser, lears, rales, reals, seral] 8: [earings, erasing, gainers, reagins, regains, reginas, searing, seringa] 8: [peris, piers, pries, prise, ripes, speir, spier, spire] 12: [apers, apres, asper, pares, parse, pears, prase, presa, rapes, reaps, spare, spear] 11: [alerts, alters, artels, estral, laster, ratels, salter, slater, staler, stelar, talers] 9: [capers, crapes, escarp, pacers, parsec, recaps, scrape, secpar, spacer] 9: [palest, palets, pastel, petals, plates, pleats, septal, staple, tepals] 9: [anestri, antsier, nastier, ratines, retains, retinas, retsina, stainer, stearin] 8: [ates, east, eats, etas, sate, seat, seta, teas] 8: [carets, cartes, caster, caters, crates, reacts, recast, traces]这里有 字典文件的样本。