Java Map集合学习笔记

一、Map集合概述

Map是Java集合框架中一个非常重要的接口,它用于存储键值对(key-value)映射关系的数据结构。与Collection接口不同,Map不继承自Collection,而是独立的接口体系。

Map的主要特点包括:

  1. 键值对存储:每个元素包含一个键(key)和一个值(value)
  2. 键唯一性:Map中不允许有重复的键
  3. 值可重复:不同的键可以对应相同的值
  4. 无序性:大多数Map实现不保证元素的顺序

Map接口的常用实现类有:

  • HashMap:基于哈希表实现,无序,允许null键和null值
  • LinkedHashMap:继承自HashMap,维护插入顺序
  • TreeMap:基于红黑树实现,按键的自然顺序或Comparator排序
  • Hashtable:线程安全的Map实现,不允许null键和null值
  • ConcurrentHashMap:线程安全的高性能Map实现

二、Map核心方法解析

1. 基本操作方法

// 添加元素
V put(K key, V value); // 添加键值对,如果键已存在则替换值并返回旧值

// 获取元素
V get(Object key); // 根据键获取值
V getOrDefault(Object key, V defaultValue); // 获取值,不存在则返回默认值

// 删除元素
V remove(Object key); // 根据键删除键值对

// 判断方法
boolean containsKey(Object key); // 判断是否包含指定键
boolean containsValue(Object value); // 判断是否包含指定值
boolean isEmpty(); // 判断Map是否为空
int size(); // 返回键值对数量

// 清空Map
void clear();

2. 视图操作方法

Map提供了三种集合视图:

  • keySet():返回所有键的Set集合
  • values():返回所有值的Collection集合
  • entrySet():返回所有键值对的Set集合
// 遍历Map的几种方式
Map<String, Integer> map = new HashMap<>();

// 1. 使用entrySet()遍历
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// 2. 使用keySet()遍历
for (String key : map.keySet()) {
    System.out.println(key + ": " + map.get(key));
}

// 3. 使用forEach方法(Java8+)
map.forEach((k, v) -> System.out.println(k + ": " + v));

// 4. 使用迭代器
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<String, Integer> entry = iterator.next();
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

三、HashMap深度解析

HashMap是Map接口最常用的实现类,基于哈希表实现,具有以下特点:

  1. 允许null键和null值
  2. 非线程安全
  3. 不保证元素顺序
  4. 初始容量为16,负载因子为0.75

1. HashMap底层结构

JDK1.8后,HashMap采用数组+链表+红黑树的结构:

  • 当链表长度超过8且数组长度大于64时,链表转为红黑树
  • 当红黑树节点数小于6时,退化为链表
// HashMap部分源码
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
    // ...
}

2. HashMap扩容机制

当元素数量超过阈值(容量*负载因子)时,HashMap会进行扩容:

  1. 创建新数组,大小为原数组的2倍
  2. 重新计算所有元素的哈希值并分配到新数组中
  3. 扩容是一个耗时的操作,应尽量避免频繁扩容
// 优化HashMap性能的建议
// 1. 预估元素数量,设置初始容量
Map<String, Integer> map = new HashMap<>(100);

// 2. 调整负载因子(特殊场景下)
Map<String, Integer> map = new HashMap<>(16, 0.5f);

四、TreeMap与排序

TreeMap是基于红黑树实现的NavigableMap,具有以下特点:

  1. 按键的自然顺序或Comparator排序
  2. 查询、插入、删除操作的时间复杂度为O(log n)
  3. 不允许null键(取决于Comparator)

1. 自然排序示例

Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("orange", 2);
treeMap.put("apple", 5);
treeMap.put("banana", 3);

// 输出顺序:apple, banana, orange
treeMap.forEach((k, v) -> System.out.println(k));

2. 自定义排序示例

// 按值排序的Comparator
Comparator<String> valueComparator = (k1, k2) -> {
    int cmp = map.get(k1).compareTo(map.get(k2));
    return cmp != 0 ? cmp : k1.compareTo(k2);
};

Map<String, Integer> sortedMap = new TreeMap<>(valueComparator);
sortedMap.putAll(map);

五、线程安全的Map实现

1. Hashtable

Hashtable是早期线程安全的Map实现:

  1. 所有方法都使用synchronized修饰
  2. 性能较差,不推荐使用
  3. 不允许null键和null值

2. ConcurrentHashMap

ConcurrentHashMap是推荐的线程安全Map实现:

  1. JDK1.7采用分段锁机制
  2. JDK1.8改用CAS+synchronized
  3. 高并发下性能优于Hashtable
// ConcurrentHashMap使用示例
ConcurrentMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put("one", 1);
concurrentMap.putIfAbsent("one", 100); // 不会替换已有值

六、Map工具类Collections

Collections类提供了一些操作Map的实用方法:

// 创建不可变Map
Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(map);

// 创建同步Map
Map<String, Integer> synchronizedMap = Collections.synchronizedMap(map);

// 空Map
Map<String, Integer> emptyMap = Collections.emptyMap();

七、Java8对Map的增强

Java8为Map接口添加了许多实用方法:

Map<String, Integer> map = new HashMap<>();

// 1. compute方法
map.compute("key", (k, v) -> v == null ? 1 : v + 1);

// 2. merge方法
map.merge("key", 1, (oldVal, newVal) -> oldVal + newVal);

// 3. getOrDefault
int value = map.getOrDefault("nonexistent", 0);

// 4. putIfAbsent
map.putIfAbsent("key", 100);

// 5. replaceAll
map.replaceAll((k, v) -> v * 2);

八、Map使用场景与最佳实践

1. 常见使用场景

  • 缓存实现
  • 数据字典
  • 对象属性映射
  • 计数器实现

2. 最佳实践

  1. 根据需求选择合适的Map实现
  2. 预估元素数量设置初始容量
  3. 高并发环境使用ConcurrentHashMap
  4. 需要排序时使用TreeMap
  5. 避免在迭代过程中修改Map

九、性能比较

不同Map实现的性能特点:

实现类获取插入删除遍历线程安全有序
HashMapO(1)O(1)O(1)O(n)
LinkedHashMapO(1)O(1)O(1)O(n)插入顺序
TreeMapO(log n)O(log n)O(log n)O(n)按键排序
HashtableO(1)O(1)O(1)O(n)
ConcurrentHashMapO(1)O(1)O(1)O(n)

十、总结

Map集合是Java中极其重要的数据结构,几乎在所有Java应用中都有使用。理解不同Map实现的特点和适用场景,能够帮助我们编写出更高效、更健壮的代码。HashMap作为最常用的实现,其底层原理值得深入理解;TreeMap提供了排序功能;而ConcurrentHashMap则是高并发环境下的首选。随着Java版本的更新,Map接口也在不断丰富其功能,如Java8引入的流式操作和函数式方法大大简化了Map的操作。

在实际开发中,应根据具体需求选择合适的Map实现,并注意线程安全和性能优化问题。掌握Map集合的使用和原理,是Java开发者必备的核心技能之一。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐