Map 集合作为 Java 双列集合的核心,其不同实现类(HashMap、LinkedHashMap、TreeMap)因底层结构差异,适用于不同业务场景。本文将结合实战代码,深入剖析三大实现类的原理、特性与用法,补充经典实战案例,并将可变参数作为拓展知识整合,助力全面掌握 Map 集合的应用。
一、HashMap 深度解析:哈希表实现,性能优先的选择
HashMap 是 Map 最常用的实现类,底层基于哈希表(JDK8 后为 “数组 + 链表 + 红黑树”),核心特性由键决定:无序、不重复、无索引,与 HashSet 底层原理完全一致(HashSet 本质是 “存储键的 HashMap”)。
1. 核心原理:键的唯一性依赖hashCode()与equals()
HashMap 通过键的hashCode()计算存储位置,通过equals()判断内容是否重复,确保键的唯一性,具体逻辑如下:
- 计算新增键的哈希值,确定其在数组中的存储位置;
- 若位置为null,直接存入键值对;
- 若位置不为null,调用equals()比较键的内容:
-
- 若equals()返回true:判定为重复键,新值覆盖旧值;
-
- 若equals()返回false:存入链表 / 红黑树(解决哈希碰撞)。
关键避坑点:若键为自定义对象(如Student),必须重写hashCode()与equals()方法,否则无法正确判断键的唯一性。
2. 实战案例:HashMap 存储自定义对象作为键
(1)案例需求
创建 HashMap,以Student对象为键(同姓名、同年龄视为同一学生),籍贯为值,存储并遍历键值对。
(2)完整实现
// 自定义Student类(重写hashCode、equals、toString)
class Student{
private String name;
private Integer age;
// 构造方法、getter/setter省略
// 重写hashCode:基于name和age计算,确保同属性对象哈希值一致
@Override
public int hashCode() {
return Objects.hash(name, age);
}
// 重写equals:同name且同age视为同一对象
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
Student student = (Student) object;
return Objects.equals(name, student.name) && Objects.equals(age, student.age);
}
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
// 测试类
public class HashMapCustomKeyDemo {
public static void main(String[] args) {
// 创建HashMap,键为Student,值为String(籍贯)
HashMap<Student, String> studentMap = new HashMap<>();
// 添加键值对(s2重复添加,会覆盖旧值)
Student s1 = new Student("张三", 18);
Student s2 = new Student("李四", 19);
studentMap.put(s1, "河南");
studentMap.put(s2, "重庆");
studentMap.put(s2, "上海"); // 重复键s2,值从"重庆"覆盖为"上海"
// 键值对遍历
for (Map.Entry<Student, String> entry : studentMap.entrySet()) {
System.out.println(entry.getKey() + " → 籍贯:" + entry.getValue());
}
}
}
(3)运行结果与说明
Student{name='张三', age=18} → 籍贯:河南
Student{name='李四', age=19} → 籍贯:上海
- 因s2重复添加,值被覆盖为 “上海”,体现键的唯一性;
- 若未重写hashCode()与equals(),s2会被视为不同键,导致重复存储。
3. 经典实战:景点选择人数统计
(1)案例需求
80 名学生从 A、B、C、D 四个景点中选一个,统计哪个景点想去的人数最多。
(2)实现思路
- 生成 80 名学生的随机景点选择;
- 用 HashMap 存储 “景点(键)- 人数(值)”,遍历统计;
- 找出人数最大值及对应景点。
(3)代码实现
public class ScenicStatisticDemo {
public static void main(String[] args) {
// 1. 定义景点数组,生成80名学生的选择
String[] scenicSpots = {"A", "B", "C", "D"};
ArrayList<String> choices = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 80; i++) {
int index = random.nextInt(scenicSpots.length);
choices.add(scenicSpots[index]);
}
// 2. HashMap统计人数
HashMap<String, Integer> countMap = new HashMap<>();
for (String spot : choices) {
if (countMap.containsKey(spot)) {
// 景点已存在,人数+1
countMap.put(spot, countMap.get(spot) + 1);
} else {
// 景点不存在,初始人数为1
countMap.put(spot, 1);
}
}
// 3. 找出最大人数
int maxCount = 0;
for (int count : countMap.values()) {
if (count > maxCount) {
maxCount = count;
}
}
// 4. 输出结果
System.out.println("想去人数最多的景点(" + maxCount + "人):");
for (Map.Entry<String, Integer> entry : countMap.entrySet()) {
if (entry.getValue() == maxCount) {
System.out.println(entry.getKey());
}
}
}
}
(4)核心逻辑
- 利用 HashMap 的 “键唯一” 特性,确保每个景点只统计一次;
- 通过containsKey()判断景点是否已统计,避免重复初始化。
二、LinkedHashMap:有序的哈希表,兼顾顺序与性能
LinkedHashMap 是 HashMap 的子类,核心改进是在哈希表基础上增加双向链表,因此具备独特特性:有序(存储与取出顺序一致)、不重复、无索引。
1. 底层原理
- 哈希表:保证键的唯一性与高效的增删查性能;
- 双向链表:记录键值对的添加顺序,实现 “有序” 特性。
2. 用法示例
public class LinkedHashMapDemo {
public static void main(String[] args) {
LinkedHashMap<String, Integer> linkedMap = new LinkedHashMap<>();
// 添加键值对(顺序:张三→李四→王五)
linkedMap.put("张三", 18);
linkedMap.put("李四", 19);
linkedMap.put("王五", 20);
linkedMap.put("李四", 21); // 重复键,覆盖值
// 遍历:输出顺序与添加顺序一致
System.out.println(linkedMap);
// 输出:{张三=18, 李四=21, 王五=20}
}
}
3. 适用场景
需要 “去重 + 保留添加顺序” 的场景,如:
- 记录用户操作日志(按操作顺序展示);
- 实现 LRU 缓存(基于链表维护访问顺序)。
三、TreeMap:红黑树实现,可排序的键值对管理
TreeMap 底层基于红黑树(自平衡二叉搜索树),核心特性由键决定:不重复、无索引、可排序,排序规则分为 “自然排序” 与 “比较器排序”。
1. 排序的两种实现方式
(1)自然排序:键实现Comparable接口
键的类需实现Comparable<T>接口,重写compareTo(T o)方法定义排序规则,返回值规则:
- 正数:当前对象排在参数对象之后;
- 负数:当前对象排在参数对象之前;
- 0:视为重复键,新值覆盖旧值。
示例:Student2按年龄升序,年龄相同按姓名字典序排序
class Student2 implements Comparable<Student2> {
private String name;
private Integer age;
// 构造方法、getter/setter/toString省略
@Override
public int compareTo(Student2 o) {
// 先按年龄升序
int ageCompare = this.age - o.age;
// 年龄相同按姓名字典序升序
return ageCompare != 0 ? ageCompare : this.name.compareTo(o.name);
}
}
// 使用自然排序的TreeMap
TreeMap<Student2, String> treeMap = new TreeMap<>();
treeMap.put(new Student2("zhang3", 18), "河南");
treeMap.put(new Student2("li4", 19), "杭州");
treeMap.put(new Student2("wang5", 18), "重庆");
System.out.println(treeMap);
// 输出:{Student2{name='zhang3', age=18}=河南, Student2{name='wang5', age=18}=重庆, Student2{name='li4', age=19}=杭州}
(2)比较器排序:创建 TreeMap 时传入Comparator
当无法修改键的类(如 Integer、String),或需临时变更排序规则时,使用比较器排序,优先级高于自然排序。
示例:TreeMap 按整数键降序排序(对应LearnLinkedHashMapAndTreeMap.java)
// 传入Comparator匿名内部类,指定降序规则
TreeMap<Integer, String> treeMap = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1; // 降序(o1-o2为升序)
}
});
// Lambda表达式简化写法
// TreeMap<Integer, String> treeMap = new TreeMap<>((o1, o2) -> o2 - o1);
treeMap.put(1, "可乐");
treeMap.put(2, "雪碧");
treeMap.put(3, "芬达");
System.out.println(treeMap); // 输出:{3=芬达, 2=雪碧, 1=可乐}
2. 核心特性与适用场景
- 特性:可排序、不重复、无索引,增删查效率为 O (logN);
- 适用场景:需要按键排序的去重场景,如:
-
- 学生成绩排名(键为成绩,值为学生信息);
-
- 商品价格区间统计(键为价格,值为商品列表)。
四、Map 三大实现类对比:如何选择?
|
实现类 |
底层结构 |
核心特性 |
增删查效率 |
适用场景 |
|
HashMap |
数组 + 链表 + 红黑树 |
无序、不重复、无索引 |
接近 O (1) |
大多数场景,优先选择 |
|
LinkedHashMap |
哈希表 + 双向链表 |
有序、不重复、无索引 |
略低于 HashMap |
需保留添加顺序的场景 |
|
TreeMap |
红黑树 |
可排序、不重复、无索引 |
O(logN) |
需按键排序的场景 |
五、额外小知识:可变参数 —— 灵活处理不确定个数的参数
可变参数解决了 “方法形参个数不固定” 的问题,允许方法接收任意个数的同类型参数,常与集合操作结合使用(如Collections.addAll()底层依赖可变参数)。
1. 定义与语法
修饰符 返回值类型 方法名(参数类型... 参数名) {
// 方法体
}
核心细节:
- 底层本质是数组,方法内部可当作数组处理;
- 一个方法中只能有一个可变参数;
- 若有多个参数,可变参数必须放在最后。
2. 用法示例(对应LearnVariableElement.java)
public class VariableParamDemo {
// 可变参数方法:求任意个数int的和
public static int getSum(int... args) {
int sum = 0;
for (int arg : args) { // 当作数组遍历
sum += arg;
}
return sum;
}
// 可变参数与普通参数共存(可变参数放最后)
public static int match(int a, int... args) {
return a + args.length;
}
public static void main(String[] args) {
System.out.println(getSum(1, 2, 3)); // 输出:6
System.out.println(getSum(1, 2, 5, 2, 35)); // 输出:45
System.out.println(match(10, 1, 2, 3)); // 输出:13(10 + 3个可变参数)
}
}
3. 常见应用场景
- 工具类方法:如求和、求平均值等不确定参数个数的操作;
- 集合批量添加:如Collections.addAll(collection, element1, element2...),简化多元素添加。
六、核心知识点总结
- HashMap:哈希表实现,键无序唯一,依赖hashCode()与equals()保证键唯一,是性能最优的默认选择;
- LinkedHashMap:HashMap 子类,通过双向链表实现有序,兼顾顺序与性能;
- TreeMap:红黑树实现,键可排序,支持自然排序与比较器排序,适用于排序场景;
- 可变参数:语法为类型... 名称,底层是数组,解决参数个数不确定问题,需放在参数列表最后。
掌握 Map 各实现类的底层差异与适用场景,结合可变参数的灵活性,能大幅提升双列集合数据处理的效率与代码质量。
634

被折叠的 条评论
为什么被折叠?



