Java 中的map - The Map Interface.

翻译来自The Map Interface

简介

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 接口的基本操作

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}

但是如果你想要结果是按照字母排序输出的话,那么仅仅需要把HashMap换成TreeMap。你将得到以下输出。
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都提供了一个可以 把另一个Map对象作为输入的构造函数,并且实例化一个人新的map对象包含着和输入map对象一样的值。以下这段代码就是实例化一个copy 对象,并且这个copy对象有着和m一模一样的值。

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


集合视图 

集合视图这个方法允许我们用三种方法打印这个Map:
1. Keyset - 这个map的key 的集合 - 是一个set
2. values - 这个map的值的集合 不是一个set 因为值可能是重复的。
3. entrySet - 这个map中的键值对。Map接口提供了一个小的嵌套式的接口Map.Entry,他是这些element的type

集合视图提供了迭代一个map的唯一的一种方法,下面的这个例子阐述了一个迭代map的常见的方法通过使用for-each

for (KeyType key : m.keySet())
    System.out.println(key);

通过使用Iterator

// 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 视图的操作被调用。
不用担心:因为map能返回一个相同的对象,当每次被要求一个Collection视图的时候。这个就是Map 的实现在java.util中做的。


调用 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 视图的一些很厉害的用法

我们知道结合视图有很多很厉害的工具 - contains all , remove all 等等。对于初学者,如果你想要知道一个map是否包含另一map那么很简单我们可以判断第一个map的keyset是否完全包含另一个的keyset。如下

if (m1.entrySet().containsAll(m2.entrySet())) {
    ...
}

相似的,如果你想要知道是否两个map的key是否是一样的那么可以使用
if (m1.keySet().equals(m2.keySet())) {
    ...
}


假设你有一个map,这个map是一个 键值对的集合,并且另外还有两个set。分别代表了 必须的键  和 允许的键(允许的键中包括了必须的键)。 以下代码片段 代表了 判断 这个map是否符合我们的规定 即 这个map中必须包含 所有  必须的键 并且不能出现 允许的键 之外的键。

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


假设你想要知道两个map的键是否有相同的键可以这样

Set<KeyType>commonKeys = new HashSet<KeyType>(m1.keySet());
commonKeys.retainAll(m2.keySet());


假设你想要从map1中移除所有map2中有的键值对可以这样

m1.entrySet().removeAll(m2.entrySet());

假设你想要从map1中移除所有map2中有的键,你这样这样

m1.keySet().removeAll(m2.keySet());


这里有一个稍微实际一点的例子,我们现在有一个map它的键是 职员的名字 它的值是 他的上司的名字。现在我们想要找到所有没有上司的人,我们应该怎么办呢?

Set<Employee> individualContributors = new HashSet<Employee>(managers.keySet());
individualContributors.removeAll(managers.values());

另外一个假设,我现在想要解雇 Simon所管理的职员我们可以这样。(Collecttions.singleton是一个静态工厂他会返回一个值为simon 的 immutable set)

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

他的意思也就是说: map 职员-》 老板。 首先我们复制这个map, 然后从一个map移除 所有满足条件的老板,这个条件是 所有的职员。因为老板也是职员,如果没有人离职的话,那么移除之后的map应该是空的。但是如果不是空的那么说明有的老板已经不在了。那么我们在通过得到这个map的键来获得这些不在的了老板的员工是哪些。

最后,我们在这里仅仅罗列了很少一部分的使用例子,当你上手了之后你就会信手拈来啦。

Multimaps 多重map

 多重map就是一个map但是他的键对应的是多个值 。java collections 框架没有一个专门接口给这个多重map,因为他不是很经常被使用。但是我们可以很简单的使用list作为一个键的值来实现 multimaps。接下来是一个例子:从一个字典文件按行读取数据,每行包含一个单词,然后我们还需要设定一个整形数,最后进行如下操作,对于每个读入的单词,我们要把他所有的字母重新进行排列组合,并且最后打印出来,打印出来的最小个数就是我们之前设定的整形数-如果重新排列组合后的字母个数不足我们设定的最小数,那么不打印该字母的组合。(从命令行需要对程序有两个输入1. 字典文件的名字2.那个整形数)如下

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]
这里有  字典文件的样本。

猜你喜欢

转载自blog.csdn.net/ytdxyhz/article/details/76980534