Java基础快速入门: Map 集合详解

本文纲要

  1. Map 集合概述与基本使用
  2. Map 集合常用方法
  3. Map 第一种遍历方式:通过 keySet 遍历
  4. Map 第二种遍历方式:通过 entrySet 遍历
  5. HashMap 原理解析
  6. HashMap 练习:自定义对象作键
  7. TreeMap 原理解析
  8. TreeMap 练习:排序与字符统计

Map 集合概述与基本使用

项目结构

mymap 
└── src 
    └── com 
        └── wb 
            ├── mapdemo1 
            │   ├── Student.java 
            │   ├── MyMap1.java 
            │   ├── MyMap2.java 
            │   ├── MyMap3.java 
            │   ├── MyMap4.java 
            │   ├── MyMap5.java 
            └── maptest 
                ├── Student.java 
                ├── Test1.java 
                └── Test2.java 

1 ) 单列集合(如 ListSet)每次添加一个元素,而 双列集合 Map 每次添加一对数据(键值对)

  • 键(Key):唯一,不可重复
  • 值(Value):可以重复
  • 键与值是一一对应的关系,一个键只能找到自己对应的值
  • 键 + 值 整体称为 键值对对象(Entry 对象)

Map 是接口,不能直接实例化,需要使用实现类,如 HashMap
泛型中需要指定两个类型:Map<K,V>

2 ) 基本使用示例(对应 MyMap1.java):

package com.wb.mapdemo1;
 
import java.util.HashMap;
import java.util.Map;
 
/*
Map的基本使用 
 */
public class MyMap1 {
    public static void main(String[] args) {
        // 使用多态创建Map对象 
        Map<String, String> map = new HashMap<>();
 
        // put方法添加元素,键是学号,值是姓名 
        map.put("itheima001", "小智");
        map.put("itheima002", "小美");
        map.put("itheima003", "大胖");
 
        System.out.println(map);
    }
}

输出结果:{itheima001=小智, itheima002=小美, itheima003=大胖}

Map 集合常用方法

Map 接口中定义的常用方法,所有实现类都可以使用。

示例类 MyMap2.java 分方法演示了 putremoveclearcontainsKeycontainsValueisEmptysize

package com.wb.mapdemo1;
 
import java.util.HashMap;
import java.util.Map;
 
public class MyMap2 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("itheima001", "小智");
        map.put("itheima002", "小美");
        map.put("itheima003", "大胖");
        map.put("itheima004", "小黑");
        map.put("itheima005", "大师");
 
        // 逐方法测试,可打开注释 
        // method1(map);
        // method2(map);
        // method3(map);
        // method4(map);
        // method5(map);
        // method6(map);
        // method7(map);
    }
 
    // 1. put:添加元素,若键已存在则覆盖原值并返回旧值 
    private static void method1(Map<String, String> map) {
        String s = map.put("itheima001", "aaa");
        System.out.println(s);      // 输出 "小智"(被覆盖的旧值)
        System.out.println(map);    // itheima001 的值变为 "aaa"
    }
 
    // 2. remove:根据键删除键值对,返回被删除的值 
    private static void method2(Map<String, String> map) {
        String s = map.remove("itheima001");
        System.out.println(s);      // 输出 "小智"
        System.out.println(map);    // itheima001 已不存在 
    }
 
    // 3. clear:清空所有键值对 
    private static void method3(Map<String, String> map) {
        map.clear();
        System.out.println(map);    // {}
    }
 
    // 4. containsKey:判断是否包含指定键 
    private static void method4(Map<String, String> map) {
        boolean result1 = map.containsKey("itheima001");
        boolean result2 = map.containsKey("itheima006");
        System.out.println(result1); // true 
        System.out.println(result2); // false 
    }
 
    // 5. containsValue:判断是否包含指定值(较少使用)
    private static void method5(Map<String, String> map) {
        boolean result1 = map.containsValue("aaa");
        boolean result2 = map.containsValue("小智");
        System.out.println(result1); // false 
        System.out.println(result2); // true 
    }
 
    // 6. isEmpty:判断集合是否为空 
    private static void method6(Map<String, String> map) {
        boolean empty1 = map.isEmpty();
        System.out.println(empty1); // false 
        map.clear();
        boolean empty2 = map.isEmpty();
        System.out.println(empty2); // true 
    }
 
    // 7. size:获取键值对个数 
    private static void method7(Map<String, String> map) {
        int size = map.size();
        System.out.println(size);   // 5 
    }
}

Map 第一种遍历方式:通过 keySet 遍历

思路:先获取所有的键(Set<K>),再通过键获取值。

核心方法:

  • Set<K> keySet():返回所有键的 Set 集合
  • V get(Object key):根据键获取对应的值
package com.wb.mapdemo1;
 
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
 
public class MyMap3 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("1号丈夫", "1号妻子");
        map.put("2号丈夫", "2号妻子");
        map.put("3号丈夫", "3号妻子");
        map.put("4号丈夫", "4号妻子");
        map.put("5号丈夫", "5号妻子");
 
        // 获取所有键 
        Set<String> keys = map.keySet();
        // 遍历键,通过 get 获取值 
        for (String key : keys) {
            String value = map.get(key);
            System.out.println(key + "---" + value);
        }
    }
}

Map 第二种遍历方式:通过 entrySet 遍历

思路:把键值对看作整体(Entry 对象),先获取所有 Entry 对象,再从中取出键和值。

核心方法:

  • Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的 Set 集合
  • K getKey():从 Entry 中获取键
  • V getValue():从 Entry 中获取值
package com.wb.mapdemo1;
 
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
 
public class MyMap4 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("1号丈夫", "1号妻子");
        map.put("2号丈夫", "2号妻子");
        map.put("3号丈夫", "3号妻子");
        map.put("4号丈夫", "4号妻子");
        map.put("5号丈夫", "5号妻子");
 
        // Set 中装的是 Map.Entry 对象,Entry 中又包含键和值 
        Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "---" + value);
        }
    }
}

延伸:Map 接口还提供了 forEach 方法,底层原理与 entrySet 遍历一致,接收 Lambda 表达式,可极大简化遍历代码(见后文示例)。

HashMap 原理解析

HashMap 底层采用 哈希表 结构(数组 + 链表 + 红黑树)。

  • 默认数组长度 16,加载因子 0.75,扩容时机:16 × 0.75 = 12(元素个数超过 12 扩容为 2 倍)
  • JDK8 起,链表长度 ≥8 且数组长度 ≥64 时,链表转为红黑树,提升查询效率

添加元素流程:

渲染错误: Mermaid 渲染失败: Parse error on line 2: ...art TD A[调用 put(K,V)] --> B[计算键的 ha ----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

关键点:

  • 依赖 hashCode()equals() 保证键的唯一性(值不参与比较)
  • 若键存储自定义对象,必须重写 hashCode()equals() 方法
  • 若键为常见类(如 StringInteger),Java 已重写好,无需处理

HashMap 练习:自定义对象作键

需求:键为学生对象(Student),值为籍贯,存储并遍历

Student 类作为键,必须重写 hashCode()equals()

package com.wb.mapdemo1;
 
import java.util.Objects;
 
public class Student {
    private String name;
    private int age;
 
    // 构造方法 
    public Student() {}
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    // getter/setter 略 
 
    @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 int hashCode() {
        return Objects.hash(name, age);
    }
 
    @Override 
    public String toString() {
        return "Student{name='" + name + "', age=" + age + "}";
    }
}

遍历示例(MyMap5.java)演示了三种遍历方式(前两种 + forEach):

package com.wb.mapdemo1;
 
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
 
public class MyMap5 {
    public static void main(String[] args) {
        HashMap<Student, String> hm = new HashMap<>();
 
        Student s1 = new Student("xiaohei", 23);
        Student s2 = new Student("dapang", 22);
        Student s3 = new Student("xiaomei", 22);
 
        hm.put(s1, "江苏");
        hm.put(s2, "北京");
        hm.put(s3, "天津");
 
        // 第一种:keySet 
        Set<Student> keys = hm.keySet();
        for (Student key : keys) {
            String value = hm.get(key);
            System.out.println(key + "----" + value);
        }
 
        System.out.println("===================================");
 
        // 第二种:entrySet 
        Set<Map.Entry<Student, String>> entries = hm.entrySet();
        for (Map.Entry<Student, String> entry : entries) {
            Student key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "----" + value);
        }
 
        System.out.println("===================================");
 
        // 第三种:forEach + Lambda 
        hm.forEach((key, value) -> System.out.println(key + "----" + value));
    }
}

TreeMap 原理解析

TreeMap 底层是红黑树,元素按照键的自然顺序或自定义比较器排序

添加流程(红黑树自动调整):

  1. 创建 Entry 节点,颜色默认红色
  2. 第一个节点作为根,并改为黑色
  3. 后续节点按排序规则插入,若违反红黑规则(如两个红色节点相连),通过变色、旋转(左旋/右旋)恢复平衡
  4. 排序时只比较键,与值无关

红黑树添加时的调整示意(仅示例类似情况):

红色

黑色/ null

新节点默认红色

是否为根节点?

染黑,完成

父节点是否为黑色?

无需调整

叔叔节点颜色?

父叔变黑,祖父变红,向上递归

根据结构旋转并变色

左旋或右旋

使用前提:

  • 键如果是自定义对象,必须实现 Comparable 接口,或在构造 TreeMap 时传入 Comparator 比较器
  • 自然排序或比较器排序只对键生效

简单示例:使用默认自然排序(String 实现了 Comparable

TreeMap<String, Integer> tm = new TreeMap<>();
tm.put("c", 3);
tm.put("a", 1);
tm.put("b", 2);
System.out.println(tm);  // {a=1, b=2, c=3}  按键排序 

TreeMap 练习:排序与字符统计

1 ) 自定义对象作为键并排序

需求:键为 Student,值为籍贯,按年龄排序,年龄相同按姓名排序。

方式一:自然排序(Comparable
Student 实现 Comparable 接口,重写 compareTo
以下代码已注释,可取消注释使用

package com.wb.maptest;
 
public class Student implements Comparable<Student> {
    private String name;
    private int age;
 
    // 构造方法、getter/setter、toString 略 
 
    @Override 
    public int compareTo(Student o) {
        // 主要条件:年龄(降序)
        int result = o.getAge() - this.getAge();
        // 次要条件:姓名 
        result = (result == 0) ? o.getName().compareTo(this.getName()) : result;
        return result;
    }
}

方式二:比较器排序(Comparator

创建 TreeMap 时传入匿名内部类

package com.wb.maptest;
 
import java.util.Comparator;
import java.util.TreeMap;
 
public class Test1 {
    public static void main(String[] args) {
        // 通过匿名内部类传入比较器 
        TreeMap<Student, String> tm = new TreeMap<>(new Comparator<Student>() {
            @Override 
            public int compare(Student o1, Student o2) {
                int result = o1.getAge() - o2.getAge();   // 升序 
                result = (result == 0) ? o1.getName().compareTo(o2.getName()) : result;
                return result;
            }
        });
 
        Student s1 = new Student("xiaohei", 23);
        Student s2 = new Student("dapang", 22);
        Student s3 = new Student("xiaomei", 22);
 
        tm.put(s1, "江苏");
        tm.put(s2, "北京");
        tm.put(s3, "天津");
 
        tm.forEach((key, value) -> System.out.println(key + "---" + value));
    }
}

2 ) 字符统计案例

需求:统计字符串 “aababcabcdabcde” 中每个字符出现次数,并按格式输出。

package com.wb.maptest;
 
import java.util.TreeMap;
 
public class Test2 {
    public static void main(String[] args) {
        String s = "aababcabcdabcde";
        TreeMap<Character, Integer> tm = new TreeMap<>();
 
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (!tm.containsKey(c)) {
                // 第一次出现 
                tm.put(c, 1);
            } else {
                // 已出现过,次数+1 
                Integer count = tm.get(c);
                count++;
                tm.put(c, count);
            }
        }
 
        // 输出 a(5)b(4)c(3)d(2)e(1)
        tm.forEach((key, value) -> System.out.print(key + "(" + value + ")"));
    }
}

输出效果:a(5)b(4)c(3)d(2)e(1)
利用 TreeMap 对键自然排序的特性,使字符按字典序排列

总结

  • Map 是双列集合,键唯一,值可重复
  • 常用方法:putremovegetcontainsKeykeySetentrySet
  • 遍历:推荐 entrySetforEach + Lambda
  • HashMap:哈希表结构,需重写 hashCodeequals 保证自定义对象键的唯一性
  • TreeMap:红黑树结构,需提供排序规则(ComparableComparator
  • 根据应用场景选择合适的实现类:无须排序用 HashMap,需要按键排序用 TreeMap
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值