本文纲要
Map集合概述与基本使用Map集合常用方法Map第一种遍历方式:通过keySet遍历Map第二种遍历方式:通过entrySet遍历HashMap原理解析HashMap练习:自定义对象作键TreeMap原理解析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 ) 单列集合(如 List、Set)每次添加一个元素,而 双列集合 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 分方法演示了 put、remove、clear、containsKey、containsValue、isEmpty、size。
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 时,链表转为红黑树,提升查询效率
添加元素流程:
关键点:
- 依赖
hashCode()和equals()保证键的唯一性(值不参与比较) - 若键存储自定义对象,必须重写
hashCode()和equals()方法 - 若键为常见类(如
String、Integer),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 底层是红黑树,元素按照键的自然顺序或自定义比较器排序
添加流程(红黑树自动调整):
- 创建
Entry节点,颜色默认红色 - 第一个节点作为根,并改为黑色
- 后续节点按排序规则插入,若违反红黑规则(如两个红色节点相连),通过变色、旋转(左旋/右旋)恢复平衡
- 排序时只比较键,与值无关
红黑树添加时的调整示意(仅示例类似情况):
使用前提:
- 键如果是自定义对象,必须实现
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是双列集合,键唯一,值可重复- 常用方法:
put、remove、get、containsKey、keySet、entrySet等 - 遍历:推荐
entrySet或forEach+ Lambda HashMap:哈希表结构,需重写hashCode与equals保证自定义对象键的唯一性TreeMap:红黑树结构,需提供排序规则(Comparable或Comparator)- 根据应用场景选择合适的实现类:无须排序用
HashMap,需要按键排序用TreeMap
751

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



