编程自学指南:java程序设计开发,Java Map 集合详解课件
一、课程信息
学习目标
- 理解 Map 集合的概念、特点和用途。
- 掌握 HashMap、TreeMap 和 LinkedHashMap 的使用方法和区别。
- 学会使用 Map 集合解决实际问题,如数据存储和查找。
- 了解 Map 集合的内部实现原理。
课程重点
- Map 集合的特点和常用方法。
- HashMap、TreeMap 和 LinkedHashMap 的使用场景。
- 键值对的存储和访问机制。
课程难点
- HashMap 的哈希冲突处理机制。
- TreeMap 的排序规则和自定义排序。
二、课程导入
生活中的映射关系
在生活中,我们经常会遇到各种映射关系。例如,学生的学号和姓名之间存在一种映射关系,一个学号对应一个学生的姓名;电话号码和联系人姓名之间也存在映射关系,一个电话号码对应一个联系人。在图书馆中,图书的编号和图书的信息也构成了映射关系。
编程中的映射需求
在 Java 编程中,我们也会经常遇到需要存储和处理映射关系的场景。例如,统计一篇文章中每个单词出现的次数,需要将单词作为键,出现的次数作为值进行存储;存储学生的成绩信息,需要将学生的姓名作为键,成绩作为值进行存储。使用普通的数组或列表来实现这些映射关系比较麻烦,而 Java 提供的 Map 集合可以很好地解决这个问题。
示例代码引入
import java.util.ArrayList;
import java.util.List;
// 模拟使用普通列表存储学生成绩信息
class StudentScore {
String name;
int score;
public StudentScore(String name, int score) {
this.name = name;
this.score = score;
}
}
public class ScoreStorageExample {
public static void main(String[] args) {
List<StudentScore> scores = new ArrayList<>();
scores.add(new StudentScore("Alice", 85));
scores.add(new StudentScore("Bob", 90));
// 查找 Alice 的成绩比较麻烦
for (StudentScore score : scores) {
if (score.name.equals("Alice")) {
System.out.println("Alice's score: " + score.score);
break;
}
}
}
}
使用 Map 集合可以更简洁地实现:
import java.util.HashMap;
import java.util.Map;
public class MapScoreStorageExample {
public static void main(String[] args) {
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 85);
scores.put("Bob", 90);
// 查找 Alice 的成绩很方便
Integer aliceScore = scores.get("Alice");
System.out.println("Alice's score: " + aliceScore);
}
}
三、Map 集合概述
定义
Map 是 Java 集合框架中的一个接口,它用于存储键值对(key - value)映射关系。每个键在 Map 中是唯一的,一个键对应一个值。
特点
- 键的唯一性:Map 中的键是唯一的,不能有重复的键。如果尝试使用相同的键存储不同的值,后面的值会覆盖前面的值。
- 无序性(部分实现):HashMap 和 LinkedHashMap 不保证键值对的插入顺序,而 TreeMap 会根据键的自然顺序或自定义顺序对键值对进行排序。
- 键和值可以为 null:在 HashMap 和 LinkedHashMap 中,键和值都可以为 null,但在 TreeMap 中,键不能为 null。
常用实现类
- HashMap:基于哈希表实现,不保证键值对的顺序,元素的存储位置由键的哈希值决定。
- TreeMap:基于红黑树实现,会根据键的自然顺序或自定义顺序对键值对进行排序。
- LinkedHashMap:基于哈希表和链表实现,既保证了键值对的唯一性,又能保持键值对的插入顺序。
四、HashMap 详解
内部实现原理
HashMap 内部使用哈希表来存储键值对。哈希表是由数组和链表(或红黑树,当链表长度超过一定阈值时)组成的。当向 HashMap 中插入一个键值对时,首先会计算键的哈希值,然后根据哈希值找到对应的桶(数组的一个位置),如果桶中没有元素,则直接将键值对放入桶中;如果桶中已经有元素,则会遍历链表(或红黑树),比较键是否相等,如果相等则更新值,否则将新的键值对插入到链表(或红黑树)中。
示例代码
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
// 创建 HashMap 对象
Map<String, Integer> ages = new HashMap<>();
// 添加键值对
ages.put("Alice", 20);
ages.put("Bob", 25);
ages.put("Charlie", 30);
// 获取值
Integer aliceAge = ages.get("Alice");
System.out.println("Alice's age: " + aliceAge);
// 判断键是否存在
boolean containsBob = ages.containsKey("Bob");
System.out.println("Contains Bob: " + containsBob);
// 删除键值对
ages.remove("Charlie");
System.out.println("After removing Charlie: " + ages);
// 遍历键值对
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
哈希冲突处理
当不同的键计算出相同的哈希值时,就会发生哈希冲突。HashMap 使用链地址法来处理哈希冲突,即每个桶中存储一个链表(或红黑树),当发生哈希冲突时,将冲突的键值对添加到链表(或红黑树)中。
键的唯一性判断
HashMap 判断键是否相等的规则是:首先比较键的哈希值,如果哈希值不同,则认为键不相等;如果哈希值相同,则调用键的 equals()
方法进一步比较。因此,为了保证键的唯一性,重写 equals()
方法时必须同时重写 hashCode()
方法。
import java.util.HashMap;
import java.util.Map;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class HashMapCustomKeyExample {
public static void main(String[] args) {
Map<Person, String> personAddresses = new HashMap<>();
Person alice = new Person("Alice", 20);
personAddresses.put(alice, "123 Main St");
personAddresses.put(new Person("Alice", 20), "456 Elm St"); // 重复键,值会覆盖
for (Map.Entry<Person, String> entry : personAddresses.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
五、TreeMap 详解
内部实现原理
TreeMap 内部使用红黑树(一种自平衡的二叉搜索树)来存储键值对。红黑树可以保证键值对按照键的顺序进行排序,并且插入、删除和查找操作的时间复杂度都是 O (log n)。
示例代码
import java.util.TreeMap;
import java.util.Map;
public class TreeMapExample {
public static void main(String[] args) {
// 创建 TreeMap 对象
Map<Integer, String> numbers = new TreeMap<>();
// 添加键值对
numbers.put(3, "Three");
numbers.put(1, "One");
numbers.put(2, "Two");
// 遍历键值对
for (Map.Entry<Integer, String> entry : numbers.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue()); // 输出按键排序的结果
}
// 获取第一个和最后一个键值对
Map.Entry<Integer, String> firstEntry = ((TreeMap<Integer, String>) numbers).firstEntry();
Map.Entry<Integer, String> lastEntry = ((TreeMap<Integer, String>) numbers).lastEntry();
System.out.println("First: " + firstEntry);
System.out.println("Last: " + lastEntry);
}
}
自然排序和自定义排序
- 自然排序:如果键实现了
Comparable
接口,TreeMap 会按照键的自然顺序进行排序。例如,Integer
、String
等类都实现了Comparable
接口。
import java.util.TreeMap;
import java.util.Map;
class Student implements Comparable<Student> {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public int compareTo(Student other) {
return Integer.compare(this.score, other.score);
}
@Override
public String toString() {
return "Student{name='" + name + "', score=" + score + "}";
}
}
public class TreeMapNaturalOrderExample {
public static void main(String[] args) {
Map<Student, String> studentGrades = new TreeMap<>();
studentGrades.put(new Student("Alice", 80), "B");
studentGrades.put(new Student("Bob", 90), "A");
studentGrades.put(new Student("Charlie", 70), "C");
for (Map.Entry<Student, String> entry : studentGrades.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
- 自定义排序:如果键没有实现
Comparable
接口,或者需要按照自定义的规则进行排序,可以在创建 TreeMap 时传入一个Comparator
对象。
import java.util.Comparator;
import java.util.TreeMap;
import java.util.Map;
class Book {
private String title;
private double price;
public Book(String title, double price) {
this.title = title;
this.price = price;
}
public String getTitle() {
return title;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return "Book{title='" + title + "', price=" + price + "}";
}
}
public class TreeMapCustomOrderExample {
public static void main(String[] args) {
// 自定义比较器,按价格排序
Comparator<Book> priceComparator = Comparator.comparingDouble(Book::getPrice);
Map<Book, String> bookCategories = new TreeMap<>(priceComparator);
bookCategories.put(new Book("Java Programming", 50.0), "Programming");
bookCategories.put(new Book("Python Basics", 30.0), "Programming");
bookCategories.put(new Book("Data Structures", 70.0), "Computer Science");
for (Map.Entry<Book, String> entry : bookCategories.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
六、LinkedHashMap 详解
内部实现原理
LinkedHashMap 是 HashMap 的子类,它在 HashMap 的基础上维护了一个双向链表,用于记录键值对的插入顺序。因此,LinkedHashMap 既保证了键值对的唯一性,又能保持键值对的插入顺序。
示例代码
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapExample {
public static void main(String[] args) {
// 创建 LinkedHashMap 对象
Map<String, Integer> cities = new LinkedHashMap<>();
// 添加键值对
cities.put("Beijing", 2000);
cities.put("Shanghai", 1800);
cities.put("Guangzhou", 1500);
// 遍历键值对
for (Map.Entry<String, Integer> entry : cities.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue()); // 按插入顺序输出
}
}
}
七、HashMap、TreeMap 和 LinkedHashMap 的比较
比较项 | HashMap | TreeMap | LinkedHashMap |
---|---|---|---|
内部实现 | 哈希表 | 红黑树 | 哈希表 + 双向链表 |
键值对顺序 | 无序 | 有序(自然排序或自定义排序) | 保持插入顺序 |
插入、删除、查找效率 | O(1) | O(log n) | O(1) |
适用场景 | 不需要键值对顺序,注重插入、删除和查找效率 | 需要键值对按键排序 | 需要保持键值对插入顺序 |
八、课堂练习
练习 1
编写一个程序,使用 HashMap 统计一个字符串中每个字符出现的次数,并输出结果。
import java.util.HashMap;
import java.util.Map;
public class Exercise1 {
public static void main(String[] args) {
String str = "hello world";
// 请完成代码
}
}
练习 2
编写一个程序,使用 TreeMap 对一组学生对象(包含学生姓名和成绩)按照成绩从高到低进行排序,并输出排序后的结果。可以通过自定义比较器来实现。
import java.util.Comparator;
import java.util.TreeMap;
import java.util.Map;
class Student {
String name;
int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{name='" + name + "', score=" + score + "}";
}
}
public class Exercise2 {
public static void main(String[] args) {
// 请完成代码
}
}
练习 3
编写一个程序,使用 LinkedHashMap 存储一组商品对象(包含商品名称和价格),并保持商品的插入顺序,最后输出这些商品的信息。
import java.util.LinkedHashMap;
import java.util.Map;
class Product {
String name;
double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Product{name='" + name + "', price=" + price + "}";
}
}
public class Exercise3 {
public static void main(String[] args) {
// 请完成代码
}
}
九、课程总结
重点回顾
- Map 集合的概念、特点和常用实现类。
- HashMap、TreeMap 和 LinkedHashMap 的内部实现原理、使用方法和区别。
- 键值对的存储和访问机制,以及键的唯一性判断。
注意事项
- 在使用 HashMap 时,要确保键的
equals()
方法和hashCode()
方法正确重写。 - 在使用 TreeMap 时,键必须实现
Comparable
接口或传入自定义的Comparator
对象,且键不能为 null。
十、课后作业
作业 1
编写一个程序,使用 HashMap 存储一组员工对象(包含员工编号、姓名和工资),并根据员工编号查找员工信息。要求重写员工类的 equals()
方法和 hashCode()
方法。
作业 2
编写一个程序,使用 TreeMap 对一组图书对象(包含图书编号、书名和价格)按照价格从低到高进行排序,并输出排序后的结果。可以通过自定义比较器来实现。
作业 3
编写一个程序,使用 LinkedHashMap 存储一组客户对象(包含客户编号、姓名和联系方式),并保持客户的插入顺序,最后输出这些客户的信息。