一、前言

<font face=黑体>集合按照其存储结构可以分为两大类,分别是单列集合 java.util.Collection 和双列集合 java.util.Map,单列集合我们已经讲了,今天我们来讲双列集合 Map

二、Map 集合

2.1、Map 集合概述

<font face=黑体>现实生活中,我们常会看到这样的一种集合:学生与学号,身份证号与个人,这种一一对应的关系,就叫做映射。Java 提供了专门的集合类用来存放这种对象关系,即 java.util.Map 接口。我们来看一下 Map 集合和 Collection 集合的区别:

  • <font face=黑体>Collection 中的集合,元素是孤立存在的,向集合中存储元素采用一个个元素的方式存储。
  • <font face=黑体>Map 中的集合,元素是成对存在的。每个元素由键与值两部分组成,通过键可以找到对应的值。
  • <font face=黑体>Collection 中的集合称为单列集合,Map 中的集合称为双列集合。
  • <font face=黑体>需要注意的是,Map 中的集合不能包含重复的键,值可以重复,每个键只能对应一个值。
<font face=黑体>Map 接口中的集合都有两个泛型变量 <K,V>,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量 <K,V> 的数据类型可以相同,也可以不同。

2.2、Map 接口常用实现类

<font face=黑体>Map 有多个子类,这里我们主要讲解常用的 HashMap 集合和 LinkedHashMap 集合。

  • <font face=黑体>HashMap<K,V>:存储数据采用的是哈希表结构,元素的存取顺序有可能不一致(无序)。由于要保证键的唯一、不重复,需要重写键的hashCode() 方法和 equals() 方法。
  • <font face=黑体>LinkedHashMap<K,V>HashMap下有个子类 LinkedHashMap,存储数据采用的是哈希表结构 + 链表结构。通过链表结构可以保证元素的存取顺序一致,通过哈希表结构可以保证键的唯一、不重复,需要重写键的hashCode() 方法和 equals() 方法。
<font face=黑体>可以类比 HashSetLinkedHashSet

2.3、Map 接口中的常用方法

<font face=黑体>Map 接口中定义了很多方法,常用的如下:

  • <font face=黑体>public V put(K key, V value): 把指定的键与指定的值添加到 Map 集合中,存储键值对的时候如果 key 不重复,返回值 v null,如果 key 重复,返回值 v 是 被替换的 value 值。

    private static void show01() {
        // 创建 Map 集合
        Map<String, String> map = new HashMap<>();
        String v1 = map.put("至尊宝", "晶晶");
        System.out.println(v1); // 返回 null
        // 这时候 晶晶 会被 紫霞仙子 替换,返回被替换的 value 值 晶晶
        String v2 = map.put("至尊宝", "紫霞仙子");
        System.out.println(v2); // 返回 晶晶
        System.out.println(map); // 返回 {至尊宝=紫霞仙子}
        
        // key 不允许重复,value 可以重复
        map.put("孙悟空", "紫霞仙子");
        System.out.println(map); // 返回 {至尊宝=紫霞仙子, 孙悟空=紫霞仙子}
    }
  • <font face=黑体>public V remove(Object key): 将指定的键所对应的元素在 Map 集合中删除,返回被删除元素的值。

    private static void show02() {
        Map<String, Integer> map = new HashMap<>();
        map.put("王祖贤", 175);
        map.put("林青霞", 168);
        map.put("李嘉欣", 172);
        System.out.println(map); // 返回 {林青霞=168, 李嘉欣=172, 王祖贤=175}
    
        Integer v1 = map.remove("李嘉欣");
        System.out.println(v1); // 返回 172
        System.out.println(map); // 返回 {林青霞=168, 王祖贤=175}
    
        Integer v2 = map.remove("李嘉诚");
        System.out.println(v2); // 返回 null
        System.out.println(map); // 返回 {林青霞=168, 王祖贤=175}
    }
  • <font face=黑体>public V get(Object key) 根据指定的键,在 Map 集合中获取对应的值。

    private static void show03() {
        Map<String, String> map = new HashMap<>();
        map.put("邓超", "孙俪");
        map.put("吴奇隆", "刘诗诗");
        map.put("黄晓明", "杨颖");
    
        String v1 = map.get("吴奇隆"); // 返回 刘诗诗
        System.out.println(v1);
    
        String v2 = map.get("胡歌"); // 返回 null
        System.out.println(v2);
    }
  • <font face=黑体>boolean containsKey(Object key) 判断集合中是否包含指定的键。

    private static void show04() {
        Map<String, String> map = new HashMap<>();
        map.put("邓超", "孙俪");
        map.put("吴奇隆", "刘诗诗");
        map.put("黄晓明", "杨颖");
    
        System.out.println(map.containsKey("邓超")); // 返回 true
        System.out.println(map.containsKey("胡歌")); // 返回 false
    }
  • <font face=黑体>public Set<K> keySet(): 获取 Map 集合中所有的键,存储到 Set 集合中。(遍历集合时具体讲解)
  • <font face=黑体>public Set<Map.Entry<K,V>> entrySet(): 获取到 Map 集合中所有的键值对对象并存储到 Set 集合中。(遍历集合时具体讲解)

2.4、Map 集合的遍历

2.4.1、通过 keySet() 方法遍历 Map 集合

<font face=黑体>通过 keySet() 方法遍历 Map 集合也称键找值方式:即通过元素中的键,获取键所对应的值。

<font face=黑体>具体步骤如下:

  1. <font face=黑体>通过 keyset() 方法获取 Map 中所有的键,并存储到一个 Set 集合中;
  2. <font face=黑体>遍历 Set 集合,得到每一个键;
  3. <font face=黑体>通过 get(K key) 方法,获取键所对应的值。

<font face=黑体>图示如下:

在这里插入图片描述

<font face=黑体>通过 keySet() 方法遍历 Map 集合代码演示如下所示:
public class MapDemo02 {

    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("邓超", "孙俪");
        map.put("吴奇隆", "刘诗诗");
        map.put("黄晓明", "杨颖");

        // 1、通过 keyset() 方法获取 Map 中所有的键,并存储到一个 Set 集合中;
        Set<String> set = map.keySet();
        // 2、遍历 Set 集合,得到每一个键;
        Iterator<String> it = set.iterator();
        while (it.hasNext()) {
            String key = it.next();
            // 3、通过 get(K key) 方法,获取键所对应的值。
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }

        System.out.println();
        System.out.println("---------------------分割线-----------------------");
        System.out.println();

        // 使用增强for
        for (String key : set) {
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }
    }
}
<font face=黑体>使用增强 for 循环简化上述遍历代码:
public class MapDemo02 {

    public static void main(String[] args) {
    
        Map<String, String> map = new HashMap<>();
        map.put("邓超", "孙俪");
        map.put("吴奇隆", "刘诗诗");
        map.put("黄晓明", "杨颖");
        
        for (String key : map.keySet()) {
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }
    }
}

2.4.2、Entry 键值对对象

<font face=黑体>EntryMap 接口中的内部接口,我们已经知道,Map 中存放的是两种对象,一种称为 key(键),一种称为 value(值),它们在Map 中是一一对应关系,这一对象又称做 Map 中的一个 Entry(项)Entry 将键值对的对应关系封装成了对象。即键值对对象,这样我们在遍历 Map 集合时,就可以从每一个键值对(Entry)对象中获取对应的键与对应的值。

<font face=黑体>Entry 表示一对键和值,提供了获取对应键和对应值的方法:

  • <font face=黑体>public K getKey():获取 Entry 对象中的键。
  • <font face=黑体>public V getValue():获取 Entry 对象中的值。

2.4.3、通过 entrySet() 方法遍历 Map 集合

<font face=黑体>在 Map 集合中提供了获取所有 Entry 对象的方法:

  • <font face=黑体>public Set<Map.Entry<K,V>> entrySet(): 获取 Map 集合中所有的键值对对象并存储到 Set 集合中。

<font face=黑体>通过 entrySet() 方法遍历 Map 集合也称键值对方式:即通过集合中每个键值对 (Entry) 对象,获取键值对对象中的键与值。

<font face=黑体>具体步骤如下:

  1. <font face=黑体>使用 Map 集合中的 entrySet() 方法,把 Map 集合中的所有 Entry 对象取出来,存储到一个 Set 集合中。
  2. <font face=黑体>遍历包含键值对对象的 Set 集合,获取每一个键值对对象。
  3. <font face=黑体>通过键值对对象的 getKey() 方法和 getValue() 方法获取键与值。
<font face=黑体>通过 entrySet() 方法遍历 Map 集合代码演示如下所示:
public class MapDemo03 {

    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("邓超", "孙俪");
        map.put("吴奇隆", "刘诗诗");
        map.put("黄晓明", "杨颖");

        // 1、使用 Map 集合中的 entrySet() 方法,把 Map 集合中的所有 Entry 对象取出来,存储到一个 Set 集合中
        Set<Map.Entry<String, String>> set = map.entrySet();
        // 2、遍历包含键值对对象的 Set 集合,获取每一个键值对对象
        Iterator<Map.Entry<String, String>> it = set.iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> entry = it.next();
            // 3、通过键值对对象的 `getKey()` 方法和 `getValue()` 方法获取键与值。
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "=" + value);
        }
    }
}
<font face=黑体>使用增强 for 循环简化上述遍历代码:
public class MapDemo03 {

    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("邓超", "孙俪");
        map.put("吴奇隆", "刘诗诗");
        map.put("黄晓明", "杨颖");

        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }
}

2.5、HashMap 存储自定义类型的键值

<font face=黑体>Map 集合要保证 key 是唯一的,即作为 key 的元素必须得重写 hashCode() 方法和 equals() 方法。

<font face=黑体>HashMap 存储自定义类型代码演示如下所示:
public class Student {

    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public static void main(String[] args) {
        //show01();
        show02();
    }

    /**
     * HashMap 存储自定义类型的键值
     * key:String 已经重写了 hashCode() 和 equals()
     * value:使用 Student value 可以重复(同名同年龄的人就视为重复)
     */
    private static void show01() {
        // 创建 hashMap 集合
        HashMap<String, Student> map = new HashMap<String, Student>();
        map.put("北京", new Student("张三", 18));
        map.put("上海", new Student("李四", 19));
        map.put("广州", new Student("王五", 20));
        map.put("北京", new Student("赵六", 18));
        // 使用 keySet() 遍历集合
        for (String key : map.keySet()) {
            System.out.println(key + "-->" + map.get(key));
        }
    }

    /**
     * HashMap 存储自定义类型的键值
     * key: 使用 Student Student 必须重写 hashCode 和 equals 方法保证 key 唯一
     * value: 使用 String
     */
    private static void show02() {
        // 创建 hashMap 集合
        HashMap<Student, String> map = new HashMap<Student, String>();
        map.put(new Student("张三", 18), "北京");
        map.put(new Student("李四", 19), "上海");
        map.put(new Student("王五", 20), "广州");
        map.put(new Student("张三", 18), "深圳");
         // 使用 entrySet() 遍历集合
        for (Map.Entry<Student, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + "-->" + entry.getValue());
        }
    }
}
<font face=黑体>打印结果如下所示,因为 String 类和 Student 类都重写了hashCode() 方法和 equals() 方法,所以存储相同的 key 的时候,后面的value 就会替换前面的 value,即 "赵六" 会替换 "张三","深圳" 会替换 "北京"。

结果

2.5、LinkedHashMap 集合

<font face=黑体>在 HashMap 下面有一个子类 LinkedHashMap,它是链表和哈希表组合的一个数据存储结构,它是有序的。

<font face=黑体>LinkedHashMap 集合代码演示如下所示:
public class LinkedHashMapDemo01 {

    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        map.put("a", "a");
        map.put("c", "c");
        map.put("b", "b");
        map.put("d", "d");
        System.out.println(map); // 返回 {a=a, b=b, c=c, d=d}

        LinkedHashMap<String, String> map1 = new LinkedHashMap<>();
        map1.put("a", "a");
        map1.put("c", "c");
        map1.put("b", "b");
        map1.put("d", "d");
        System.out.println(map1); // 返回 {a=a, c=c, b=b, d=d}
    }
}

2.6、HashTable 集合

<font face=黑体>HashTable 也是继承自 Map 接口的,底层也是一个哈希表,跟 HashMap 的区别如下:

  1. <font face=黑体>键和值都不允许存储 null
  2. <font face=黑体>线程安全,速度慢。

<font face=黑体>HashTable 集合 和 Vector 集合一样,已经被更先进的集合(HashMapArrayList)所取代。

<font face=黑体>HashTable 集合代码演示如下所示:
public class HashTableDemo01 {

    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<String, String>();
        map.put(null, "a");
        map.put("b", null);
        map.put(null, null);
        System.out.println(map); // 返回 {null=null, b=null}

        Hashtable<String, String> map2 = new Hashtable<String, String>();
        map2.put(null, "a"); // 报错 NullPointerException
        map2.put("b", null); // 报错 NullPointerException
        map2.put(null, null); // 报错 NullPointerException
    }
}

三、Map 集合练习

<font face=黑体>需求:

<font face=黑体>计算一个字符串中每个字符出现的次数。

<font face=黑体>实现步骤:

  1. <font face=黑体>获取一个字符串对象;
  2. <font face=黑体>创建一个 Map 集合,键代表字符,值代表次数;
  3. <font face=黑体>遍历字符串得到每个字符;
  4. <font face=黑体>判断 Map 中是否有该键;
  5. <font face=黑体>如果没有,第一次出现,存储次数为 1;如果有,则说明已经出现过,获取到对应的值进行 ++,再次存储;
  6. <font face=黑体>打印最终结果。

    <font face=黑体>代码实现:

public class ExampleDemo01 {

    public static void main(String[] args) {
        // 1、使用 Scanner 获取一个字符串对象;
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String str = sc.next();

        // 2、创建一个 Map 集合,键代表字符,值代表次数;
        HashMap<Character, Integer> map = new HashMap();

        // 3、遍历字符串得到每个字符;
        for (char c : str.toCharArray()) {
            // 4、判断 Map 中是否有该键;
            if (map.containsKey(c)) {
                Integer value = map.get(c);
                value++;
                map.put(c, value);
            } else {
                map.put(c, 1);
            }
        }

        // 5、打印最终结果。
        for (Character key : map.keySet()) {
            System.out.println(key + "-->" + map.get(key));
        }
    }
}

四、源码

<font face=黑体>文章中用到的所有源码已上传至 github,有需要的可以去下载。


Maenj_Ba_lah
28 声望5 粉丝

真正的大师,永远怀着一颗学徒的心。