大家好呀!今天我们来聊聊Java中超级重要的Map集合家族 。Map就像是一个神奇的魔法口袋,可以帮我们把东西(值)和标签(键)一一对应存放起来。不管你是Java新手还是老司机,掌握Map都是必修课!这篇超长干货会带你彻底搞懂HashMap、TreeMap、LinkedHashMap等常用Map的实现原理和使用技巧,保证让你收获满满!
想象你有一个神奇的电话本:
这就是Map的本质!它存储的是键值对(Key-Value Pair),通过键就能快速找到对应的值,就像查字典一样方便。
Map phoneBook = new HashMap<>();
phoneBook.put("张三", "13800138000");
phoneBook.put("李四", "13900139000");
System.out.println(phoneBook.get("张三")); // 输出:13800138000
Java中常见的Map实现类:
实现类 | 特点描述 | 适用场景 |
---|---|---|
HashMap | 查询快,无序存储 | 最常用,需要快速存取 |
LinkedHashMap | 保留插入顺序 | 需要保持插入或访问顺序 |
TreeMap | 自动按键排序 | 需要有序遍历 |
Hashtable | 线程安全但性能较低 | 多线程环境(基本被淘汰) |
ConcurrentHashMap | 高性能线程安全Map | 高并发场景 |
HashMap就像一个大仓库,里面有很多小柜子(桶bucket)。当你存放东西时:
1️⃣ 计算位置:根据key的hashCode()计算应该放在哪个柜子
2️⃣ 处理冲突:如果多个key算到同一个柜子,就用链表或红黑树存起来
3️⃣ 动态扩容:当东西太多时,仓库会自动扩大(默认扩容到2倍)
// HashMap的简单实现原理伪代码
class MyHashMap {
Node[] table; // 存放数据的数组
void put(K key, V value) {
int hash = hash(key); // 计算哈希值
int index = hash % table.length; // 计算存储位置
if (table[index] == null) {
table[index] = new Node(key, value); // 直接存放
} else {
// 处理哈希冲突(链表或红黑树)
}
}
}
初始容量:默认16,创建时可以指定
new HashMap(32); // 初始容量32
负载因子(Load Factor):默认0.75,表示当容量使用75%时就会扩容
new HashMap(16, 0.5f); // 负载因子设为0.5
树化阈值:链表长度超过8时可能转为红黑树
HashMap在JDK8做了重大升级:
Map scoreMap = new HashMap<>();
// 添加元素
scoreMap.put("数学", 90);
scoreMap.put("语文", 85);
// 获取元素
int mathScore = scoreMap.get("数学");
// 遍历(无序!)
scoreMap.forEach((subject, score) ->
System.out.println(subject + ": " + score));
LinkedHashMap是HashMap的亲儿子,它在HashMap基础上:
插入顺序(默认):按put的先后顺序
Map orderedMap = new LinkedHashMap<>();
访问顺序:最近访问的排到后面,适合实现LRU缓存
Map lruMap = new LinkedHashMap<>(16, 0.75f, true);
class LRUCache extends LinkedHashMap {
private final int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > capacity; // 超过容量移除最久未使用的
}
}
// 使用示例
LRUCache cache = new LRUCache<>(3);
cache.put("1", "A");
cache.put("2", "B");
cache.put("3", "C");
cache.get("1"); // 访问"1"使其变为最近使用
cache.put("4", "D"); // 此时"2"会被移除
TreeMap底层使用红黑树(一种自平衡二叉查找树)实现:
自然排序:Key实现Comparable接口
Map treeMap = new TreeMap<>();
定制排序:创建时传入Comparator
Map customOrderMap = new TreeMap<>(Comparator.reverseOrder());
TreeMap ageMap = new TreeMap<>();
ageMap.put(25, "张三");
ageMap.put(30, "李四");
ageMap.put(20, "王五");
// 获取第一个和最后一个
System.out.println(ageMap.firstKey()); // 20
System.out.println(ageMap.lastKey()); // 30
// 范围查询
Map subMap = ageMap.subMap(22, 28); // 22≤key<28
特性 | Hashtable | ConcurrentHashMap |
---|---|---|
锁粒度 | 整个表锁 | 分段锁(JDK7)/CAS+synchronized(JDK8) |
性能 | 低 | 高 |
是否允许null键值 | 不允许 | 不允许 |
迭代器 | 强一致性 | 弱一致性 |
ConcurrentHashMap counter = new ConcurrentHashMap<>();
// 线程安全的累加操作
counter.compute("click", (k, v) -> v == null ? 1 : v + 1);
// 批量操作
counter.search(2, (k, v) -> v > 100 ? k : null); // 并行搜索
操作 | HashMap | LinkedHashMap | TreeMap | ConcurrentHashMap |
---|---|---|---|---|
插入 | O(1) | O(1) | O(log n) | O(1) |
查找 | O(1) | O(1) | O(log n) | O(1) |
删除 | O(1) | O(1) | O(log n) | O(1) |
遍历 | 无序 | 有序 | 有序 | 弱一致 |
可变对象作为Key:如果Key对象在放入Map后发生改变,会导致找不到!
Map, String> map = new HashMap<>();
List key = new ArrayList<>(Arrays.asList("a"));
map.put(key, "value");
key.add("b"); // 修改key
System.out.println(map.get(key)); // 可能返回null!
初始容量设置:预估元素数量,避免频繁扩容
// 预计存放1000个元素,负载因子0.75
new HashMap<>(1333); // 1000/0.75 ≈ 1333
hashCode()与equals():作为Key的类必须正确重写这两个方法
// 使用LinkedHashMap实现LRU商品缓存
public class ProductCache {
private static final int MAX_ITEMS = 1000;
private final LinkedHashMap cache;
public ProductCache() {
this.cache = new LinkedHashMap(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ITEMS;
}
};
}
public Product getProduct(long id) {
return cache.get(id);
}
public void addProduct(Product product) {
cache.put(product.getId(), product);
}
}
// 使用HashMap统计单词出现次数
public Map wordCount(String text) {
Map freqMap = new HashMap<>();
String[] words = text.split("\\W+");
for (String word : words) {
freqMap.merge(word.toLowerCase(), 1, Integer::sum);
}
return freqMap;
}
️ 主要区别:
️ 扩容过程:
️ 不同版本实现:
✔️ HashMap:最常用,O(1)时间复杂度,无序
✔️ LinkedHashMap:保持插入/访问顺序,适合LRU
✔️ TreeMap:红黑树实现,自动排序,O(log n)操作
✔️ ConcurrentHashMap:高并发场景首选
希望这篇超详细的Map指南能帮你彻底掌握Java Map家族!如果有任何问题,欢迎随时讨论交流~
Happy Coding!
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
HTTP、HTTPS、Cookie 和 Session 之间的关系
什么是 Cookie?简单介绍与使用方法
什么是 Session?如何应用?
使用 Spring 框架构建 MVC 应用程序:初学者教程
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
如何理解应用 Java 多线程与并发编程?
把握Java泛型的艺术:协变、逆变与不可变性一网打尽
Java Spring 中常用的 @PostConstruct 注解使用总结
如何理解线程安全这个概念?
理解 Java 桥接方法
Spring 整合嵌入式 Tomcat 容器
Tomcat 如何加载 SpringMVC 组件
“在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”
“避免序列化灾难:掌握实现 Serializable 的真相!(二)”
如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)
解密 Redis:如何通过 IO 多路复用征服高并发挑战!
线程 vs 虚拟线程:深入理解及区别
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
“打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”
Java 中消除 If-else 技巧总结
线程池的核心参数配置(仅供参考)
【人工智能】聊聊Transformer,深度学习的一股清流(13)
Java 枚举的几个常用技巧,你可以试着用用
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
HTTP、HTTPS、Cookie 和 Session 之间的关系
使用 Spring 框架构建 MVC 应用程序:初学者教程
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
Java Spring 中常用的 @PostConstruct 注解使用总结
线程 vs 虚拟线程:深入理解及区别
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)
为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)