1. Map接口概述
Map是Java集合框架中的核心接口之一,用于存储键值对(key-value pairs)。与List和Set不同,Map不是Collection接口的子接口,而是一个独立的顶级接口。Map中的每个键都是唯一的,但值可以重复。
Map的基本特性
- 键(Key)必须唯一,值(Value)可以重复
- 一个键只能映射到一个值
- 不同键可以映射到相同的值
- 键和值都可以为null(取决于具体实现)
2. Map接口的核心方法
基本操作方法
// 添加键值对
V put(K key, V value)
// 获取指定键的值
V get(Object key)
// 移除指定键的键值对
V remove(Object key)
// 检查是否包含指定键
boolean containsKey(Object key)
// 检查是否包含指定值
boolean containsValue(Object value)
// 获取Map大小
int size()
// 检查Map是否为空
boolean isEmpty()
// 清空Map
void clear()
批量操作方法
// 将另一个Map的所有键值对添加到当前Map
void putAll(Map<? extends K, ? extends V> m)
// 获取所有键的集合
Set<K> keySet()
// 获取所有值的集合
Collection<V> values()
// 获取所有键值对的集合
Set<Map.Entry<K, V>> entrySet()
3. 主要实现类详解
3.1 HashMap
HashMap是Map接口最常用的实现类,基于哈希表实现。
特点:
- 允许null键和null值
- 非线程安全
- 无序存储
- 平均时间复杂度:O(1)
内部结构:
- JDK 1.8之前:数组 + 链表
- JDK 1.8及之后:数组 + 链表 + 红黑树
// HashMap示例
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("apple", 10);
hashMap.put("banana", 20);
hashMap.put("orange", 15);
// 遍历HashMap
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
3.2 LinkedHashMap
LinkedHashMap继承自HashMap,维护了插入顺序或访问顺序。
特点:
- 保持插入顺序或访问顺序
- 基于哈希表和双向链表实现
- 性能略低于HashMap
// LinkedHashMap示例
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("first", 1);
linkedHashMap.put("second", 2);
linkedHashMap.put("third", 3);
// 输出顺序与插入顺序一致
linkedHashMap.forEach((k, v) -> System.out.println(k + ": " + v));
3.3 TreeMap
TreeMap基于红黑树实现,是有序的Map。
特点:
- 根据键的自然顺序或自定义Comparator排序
- 不允许null键,但允许null值
- 时间复杂度:O(log n)
// TreeMap示例
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("zebra", 26);
treeMap.put("apple", 1);
treeMap.put("banana", 2);
// 按键的字典序输出
treeMap.forEach((k, v) -> System.out.println(k + ": " + v));
// 输出: apple: 1, banana: 2, zebra: 26
3.4 ConcurrentHashMap
ConcurrentHashMap是线程安全的HashMap实现。
特点:
- 线程安全
- 高并发性能
- 不允许null键和null值
- 使用分段锁机制(JDK 1.8改为CAS + synchronized)
// ConcurrentHashMap示例
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put("thread1", 1);
concurrentMap.put("thread2", 2);
// 原子操作
concurrentMap.putIfAbsent("thread3", 3);
concurrentMap.computeIfAbsent("thread4", k -> 4);
4. 高级特性和方法
4.1 JDK 1.8新增方法
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
// getOrDefault - 获取值,如果不存在返回默认值
Integer value = map.getOrDefault("c", 0); // 返回0
// putIfAbsent - 如果键不存在则添加
map.putIfAbsent("c", 3);
// replace - 替换指定键的值
map.replace("a", 10);
// compute - 计算新值
map.compute("a", (k, v) -> v * 2); // a的值变为20
// merge - 合并值
map.merge("d", 1, (oldVal, newVal) -> oldVal + newVal);
4.2 Stream API结合使用
Map<String, Integer> map = new HashMap<>();
map.put("apple", 10);
map.put("banana", 20);
map.put("orange", 15);
// 过滤并收集到新Map
Map<String, Integer> filtered = map.entrySet().stream()
.filter(entry -> entry.getValue() > 10)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
));
// 转换值
Map<String, String> transformed = map.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> "Count: " + entry.getValue()
));
5. 性能比较和选择建议
性能对比表
实现类 | 查找 | 插入 | 删除 | 有序性 | 线程安全 |
---|---|---|---|---|---|
HashMap | O(1) | O(1) | O(1) | 无 | 否 |
LinkedHashMap | O(1) | O(1) | O(1) | 插入顺序 | 否 |
TreeMap | O(log n) | O(log n) | O(log n) | 键排序 | 否 |
ConcurrentHashMap | O(1) | O(1) | O(1) | 无 | 是 |
选择建议
使用HashMap当:
- 需要最快的查找、插入、删除操作
- 不需要保持顺序
- 单线程环境
使用LinkedHashMap当:
- 需要保持插入顺序或访问顺序
- 需要实现LRU缓存
使用TreeMap当:
- 需要按键排序
- 需要范围查询功能
使用ConcurrentHashMap当:
- 多线程环境
- 需要高并发性能
6. 最佳实践和注意事项
6.1 正确重写hashCode()和equals()
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
6.2 初始容量设置
// 如果已知Map的大小,设置初始容量可以提高性能
Map<String, Integer> map = new HashMap<>(16);
// 对于已知大小的数据,计算合适的初始容量
int expectedSize = 100;
int initialCapacity = (int) (expectedSize / 0.75) + 1;
Map<String, Integer> optimizedMap = new HashMap<>(initialCapacity);
6.3 避免并发修改异常
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
// 错误的做法 - 会抛出ConcurrentModificationException
// for (String key : map.keySet()) {
// if (key.equals("a")) {
// map.remove(key);
// }
// }
// 正确的做法 - 使用Iterator
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
if (entry.getKey().equals("a")) {
iterator.remove();
}
}
6.4 使用不可变Map
// 创建不可变Map
Map<String, Integer> immutableMap = Map.of(
"apple", 10,
"banana", 20,
"orange", 15
);
// 或者使用Collections.unmodifiableMap()
Map<String, Integer> originalMap = new HashMap<>();
originalMap.put("a", 1);
Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(originalMap);
7. 实际应用场景
7.1 缓存实现
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int maxSize;
public LRUCache(int maxSize) {
super(16, 0.75f, true); // accessOrder = true
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
}
7.2 统计词频
public Map<String, Integer> countWords(String text) {
Map<String, Integer> wordCount = new HashMap<>();
String[] words = text.toLowerCase().split("\\s+");
for (String word : words) {
wordCount.merge(word, 1, Integer::sum);
}
return wordCount;
}
7.3 分组操作
// 将学生按年级分组
List<Student> students = getStudents();
Map<String, List<Student>> studentsByGrade = students.stream()
.collect(Collectors.groupingBy(Student::getGrade));