Java Map完全指南:从基础到高级应用

发布于:2025-06-09 ⋅ 阅读:(18) ⋅ 点赞:(0)


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));

网站公告

今日签到

点亮在社区的每一天
去签到