目录
一、引言
二、Map接口概述
1.常用方法
2.实现Map的主要类
2.1.HashMap
2.2.LinkedHashMap
2.3.Hashtable(遗留类,已过时)
2.4.TreeMap
2.5.ConcurrentHashMap
3.总结对比
4.不常用的实现类
4.1.IdentityHashMap
4.2.WeakHashMap
4.3.EnumMap
4.4.ConcurrentSkipListMap
5.了解SynchronizedMap
三、Map的遍历与去重
1.Map 的四种主要遍历方式
1.1.使用entrySet()遍历键值对
1.2.使用keySet()遍历键
1.3.使用values()遍历值
1.4.使用Java 8的forEach方法
2.Map去重方法
四、结语
在Java编程中,Map接口是处理键值对数据的核心工具之一。它广泛应用于缓存、配置管理、数据映射等场景。无论是初学者还是有一两年开发经验的程序员,掌握Map接口及其常用实现类的使用都是非常重要的。
本文将详细解析Java中的Map接口,包括其基本概念、常用方法、主要实现类、遍历方式以及不同实现类的适用场景和注意事项。通过实际代码示例,帮助读者更好地理解和应用Map。
以下是一个展示Java中常用Map集合的思维导图:
Map接口是Java集合框架中的一部分,用于存储键值对(key-value pairs)。与List和Set不同,Map不继承自Collection接口,而是独立存在。Map中的每个元素都是一个键值对,其中键(key)是唯一的,而值(value)可以重复。Map接口允许通过键来快速查找、插入和删除对应的值。键的唯一性确保了每个键只能映射到一个值,但不同的键可以映射到相同的值。
put(K key, V value):将键值对放入Map。
get(Object key):根据键获取对应的值。
remove(Object key):根据键删除键值对。
containsKey(Object key):判断是否包含指定的键。
containsValue(Object value):判断是否包含指定的值。
size():返回Map中键值对的数量。
isEmpty():判断Map是否为空。
keySet():返回所有键的集合。
values():返回所有值的集合。
entrySet():返回所有键值对的集合。
// 创建一个HashMap实例
Map map = new HashMap<>();
// put方法:添加键值对
map.put("apple", 3);
map.put("banana", 5);
// get方法:获取键对应的值
System.out.println("apple的数量: " + map.get("apple")); // 输出:3
// containsKey方法:检查是否包含某个键
System.out.println("是否包含键'banana': " + map.containsKey("banana")); // 输出:true
// remove方法:删除键值对
map.remove("banana");
// size方法:获取Map的大小
System.out.println("当前Map的大小: " + map.size()); // 输出:1
// isEmpty方法:检查Map是否为空
System.out.println("Map是否为空: " + map.isEmpty()); // 输出:false
HashMap 是基于哈希表实现的,允许存储键值对,键和值都可以为 null。它不保证元素的顺序,且是非线程安全的。
特点:
查找、插入和删除速度快。
支持null键和null值 ,无序存储。
非线程安全。
使用场景:
适用于需要快速查找、插入和删除的场景,且不关心元素的顺序。
示例代码:
Map map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 5);
System.out.println("apple的数量: " + map.get("apple")); // 输出:3
System.out.println("是否包含banana: " + map.containsKey("banana")); // 输出:true
LinkedHashMap继承自HashMap是另一个重要的List实现,它基于双向链表实现,对于插入和删除操作具有较高的性能,但对于随机访问效率较低。
特点:
支持高效的插入和删除操作 ,保持插入和访问顺序。
非线程安全。
可以作为堆栈、队列或双端队列使用。
使用场景:
适用于需要保持插入顺序或访问顺序的场景。
示例代码:
Map map = new LinkedHashMap<>();
map.put("apple", 3);
map.put("banana", 5);
for (Map.Entry entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 输出顺序与插入顺序一致
早期的线程安全 Map 实现,它通过同步方法来实现线程安全。
特点:
线程安全,但性能较低。
所有方法都使用synchronized修饰。
不允许null键和null值。
使用场景:
适用于需要线程安全的场景,但性能不如 ConcurrentHashMap。
示例代码:
Map map = new Hashtable<>();
map.put("apple", 3);
map.put("banana", 5);
System.out.println("apple的数量: " + map.get("apple")); // 输出:3
TreeMap 是基于红黑树实现的,它保持键的自然顺序或自定义顺序
特点:
按照键的自然顺序或自定义顺序排序。
不允许null键,但允许null值。
查找、插入和删除速度快。
非线程安全。
使用场景:
适用于需要按键排序的场景。
示例代码:
Map map = new TreeMap<>();
map.put("banana", 5);
map.put("apple", 3);
for (Map.Entry entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 输出按键的字母顺序排列
ConcurrentHashMap 是线程安全的 HashMap 实现。
版本演变:
JDK 1.7 及之前使用分段锁(Segment)机制,多个锁控制不同 Segment,降低锁粒度。
JDK 1.8 及以后采用 CAS + synchronized + 红黑树 的机制,基于Node数组+链表+红黑树,每个桶自己加锁。
特点:
不允许null键和null值。
线程安全且性能高,支持高并发访问。
使用场景:
适用于需要在多线程环境中高效地进行并发操作。
适用于需要线程安全且高性能的Map。
示例代码:
public static void main(String[] args) throws InterruptedException {
Map map = new ConcurrentHashMap<>();
Runnable putTask = () -> {
for (int i = 0; i < 1000; i++) {
map.put(i, "value" + i);
}
};
Runnable getTask = () -> {
for (int i = 0; i < 1000; i++) {
map.get(i);
}
};
Thread t1 = new Thread(putTask);
Thread t2 = new Thread(getTask);
Thread t3 = new Thread(putTask);
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println("Map大小: " + map.size()); // 输出:1000(最终唯一)
特点:基于引用地址判断键是否相等。
使用场景:适用于需要精确对象比较的场景。
特点:键为弱引用,易被GC回收。
使用场景:适合做缓存或临时映射。
特点:键必须是 枚举类型(enum),不允许 null 键,但允许 null 值。
使用场景:枚举作为键的映射关系管理。
特点:基于跳表(SkipList)实现的有序并发 Map,支持范围查询,支持高并发读写操作。
使用场景:适用于多线程下需排序的场景。
它不是独立的 Map 实现类,而是通过 Collections 工具类将普通 Map 包装成线程安全的 Map。
特点:
线程安全:通过在每个方法上加锁(synchronized)实现同步。
非并发专用:适用于并发量不高的场景,性能不如 ConcurrentHashMap。
可包装任意 Map 实现:可以包装 HashMap、TreeMap 等任何实现了 Map 接口的类。
锁粒度大:每次操作都会锁定整个 Map,高并发下容易成为瓶颈。
使用场景:
单线程或低并发环境下的线程安全需求。
不希望引入额外依赖时使用标准库提供的工具。
快速将已有 Map 变为线程安全,但不要求高性能。
示例代码:
// 创建一个普通HashMap
Map map = new HashMap<>();
// 转换为线程安全的Map
Map synchronizedMap = Collections.synchronizedMap(map);
// 多线程环境下使用该map是线程安全的
synchronizedMap.put("key", "value");
System.out.println(synchronizedMap.get("key"));
Map map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 5);
// 使用entrySet遍历键值对
for (Map.Entry entry : map.entrySet()) {
System.out.println("键: " + entry.getKey() + ", 值: " + entry.getValue());
}
Map map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 5);
// 使用keySet遍历键
for (String key : map.keySet()) {
System.out.println("键: " + key + ", 值: " + map.get(key));
}
Map map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 5);
// 使用values遍历值
for (Integer value : map.values()) {
System.out.println("值: " + value);
}
Map map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 5);
// 使用Java 8的forEach遍历
map.forEach((key, value) -> System.out.println("键: " + key + ", 值: " + value));
键自动唯一,不会重复存储。键重复了新值会覆盖旧值。
示例代码:
Map map = new HashMap<>();
map.put("apple", 1);
map.put("apple", 2); // 重复的 key,会被覆盖
System.out.println(map); // 输出:{apple=2}
Map是Java中非常重要的集合类型,理解其核心概念、常用方法以及不同实现类的特点对于编写高效的Java程序至关重要。本文从基础到进阶,详细介绍了Map接口的常用方法、遍历方式以及各个实现类的使用场景和注意事项。希望本文能够帮助初学者和有一定开发经验的程序员更好地掌握Map的使用,提升编程能力