Map接口

2021-04-21  本文已影响0人  Mcq

一个Map 是一个键值对对象。
一个map不能包含重复的键:每个键最多可以映射到一个值,它对数学函数抽象进行了建模。

Map接口基础操作

put
get
remove
containsKey
containsValue
size
empty

批量操作

putAll
clear

集合视图

keySet
entrySet
values

Java平台包含三种Map实现:HashMap(无序)、TreeMap(按键的字母顺序顺序)和LinkedHashMap(原顺序),它们的行为和性能类似于HashSet、TreeSet和LinkedHashSet,如 The Set Interface一节所述。

JDK8聚合操作收集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)));

按成绩及格与否,将学生分组:

// Partition students into passing and failing
Map<Boolean, List<Student>> passingFailing = students.stream()
.collect(Collectors.partitioningBy(s -> s.getGrade()>= PASS_THRESHOLD)); 

按城市进行分组:

// Cascade Collectors 
Map<String, Map<String, List<Person>>> peopleByStateAndCity
  = personStream.collect(Collectors.groupingBy(Person::getState,
  Collectors.groupingBy(Person::getCity)))

Map接口基础操作

Map的基本操作(put、get、containsKey、containsValue、size和isEmpty)与Hashtable中的对应操作完全相同。

一个单词频次表的例子:

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

与set和Listinterfaces类似,Map加强了对equals和hashCode方法,以便比较两个Map对象的逻辑相等性,而不必考虑它们的实现类型。两个Map的键值都相等,则两者相等。

通常所有通用Map实现的构造函数接受Map对象并初始化新Map,相当于复制一个相同的Map。这个标准Map转换构造函数完全类似于标准Collection 构造函数。

Map<K, V> copy = new HashMap<K, V>(m);

Map接口批量接口

clear 删除所有元素
putAll 类似于Collection的addAll,与构造函数一起使用可实现按默认值创建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;
}

集合视图

Collection views 方法可以把Map视为Collection:

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

迭代键值对

for (Map.Entry<KeyType, ValType> e : m.entrySet())
    System.out.println(e.getKey() + ": " + e.getValue());

最初,许多人担心这些习惯用法可能很慢,因为每次调用集合视图操作时,映射都必须创建一个新的Collection 实例。大可放心:每次请求给定的集合视图时,Map总是返回相同的对象,这正是java中所有Map实现在java.util中做的。

三种Collection 视图中,Iterator的 remove 和 entrySet视图的Map.Entry's setValue 是Map迭代过程中的安全操作,其他操作行为可能是不明确的。

Collection视图支持移除操作, remove, removeAll, retainAll和 clear 。
在任何情况下,Collection视图都不支持添加元素,因为Map的put和putAll方法已经提供了相同的功能。

Collection视图的奇妙用法:映射代数

判断一个Map是否为另一个Map的子集,即包含所有键值对:

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

判断两个Map是否相同:

if (m1.keySet().equals(m2.keySet())) {
    ...
}

假设你有一个Map和两个分别表示必须属性和允许属性的Set,判断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公共的key:

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

之前的所有操作都没有改变原Map结构的,但有操作会改变原Map,比如,删除一个Map中它和另一个Map的相同键值对:

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

比如,删除一个Map中它和另一个Map的相同键:

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

假设有一个Map,映射员工和管理者的关系,找出没有管理者的员工:

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

开除Simon的所有下属:

Employee simon = ... ;
managers.values().removeAll(Collections.singleton(simon));

所属管理者不再为公司工作的员工:

Map<Employee, Employee> m = new HashMap<Employee, Employee>(managers);
m.values().removeAll(managers.keySet());
Set<Employee> slackers = m.keySet();

Multimaps多重映射

multimap类似于Map,但它可以将每个键映射到多个值。

multimap类似于Map,但它可以将每个键映射到多个值。Java Collections框架没有包含用于multimap的接口,因为它们并不经常使用。把值类型为List的Map用作multimap,是一件相当简单的事情。下一个代码示例将演示这种技术,该代码示例读取每行包含一个单词的单词列表(全部小写),并打印出满足大小标准的所有字谜组。字谜组是一堆单词,它们都包含完全相同的字母,但顺序不同。
该程序在命令行上接受两个参数:(1)字典文件的名称和(2)要打印的字谜组的最小大小。包含少于指定最小字数的字谜组不会被打印。

有一个查找字谜组的标准技巧:对于字典中的每个单词,将单词中的字母按字母顺序排列(即将单词的字母按字母顺序重新排列),并将一个条目放入multimap中,将按字母顺序排列的单词映射到原始单词。例如,bad这个词会导致将abd映射为bad的条目被放入multimap中。稍作思考,你就会发现,所有的字词,任何给定的关键图都可以组成一个字词组合。遍历multimap中的键,打印出满足大小约束的每个字谜组,是一件很简单的事情。


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);
    }
}
上一篇下一篇

猜你喜欢

热点阅读