Java Map 集合详解
Map是Java集合框架中用于存储键值对的数据结构,它不继承自Collection接口,是一个独立的接口。Map集合在Java开发中应用广泛,从简单的数据缓存到复杂的业务逻辑处理,都离不开Map的支持。
Map接口 = 键值对存储 + 键唯一性 + 高效查找 + 灵活操作 + 多种实现选择
- 🔑 键值对存储:每个元素包含键(Key)和值(Value),形成一对一映射关系
- 🔍 键唯一性:每个键在Map中只能出现一次,重复添加会覆盖原值
- ⚡ 高效查找:大多数实现提供O(1)查找性能,适合频繁检索
- 🛠️ 灵活操作:支持基于键的增删改查和集合视图操作
- 📦 丰富实现:HashMap、TreeMap、LinkedHashMap等多种实现满足不同需求
1. Map接口基础概念
1.1 什么是Map接口?
Map接口是Java集合框架中的核心接口,用于存储键值对(key-value pair)数据。
Map集合具有以下核心特征:
- 键值对存储:每个元素包含一个键和一个值
- 键唯一性:键不能重复,值可以重复
- 高效查找:基于键的快速查找,时间复杂度O(1)
- 灵活操作:支持增删改查、批量操作等
- 多种实现:提供多种实现类满足不同需求
1.2 Map接口的重要性
| 重要性 | 具体体现 | 业务价值 |
|---|---|---|
| 数据关联 | 建立键与值之间的映射关系 | 支持复杂的数据关联需求 |
| 快速查找 | 基于键的O(1)查找性能 | 提高数据检索效率 |
| 缓存实现 | 天然适合缓存数据结构 | 支持各种缓存场景 |
| 配置管理 | 键值对形式的配置存储 | 简化配置管理逻辑 |
1.3 Map接口设计原则
Map接口的设计遵循以下几个核心原则:
键值对原则
提供键与值之间的映射关系,支持基于键的快速访问
键唯一性原则
确保键的唯一性,避免数据冲突和覆盖
高效查找原则
基于哈希算法实现高效的查找和访问
灵活操作原则
支持丰富的操作方法,满足各种使用场景
1public interface Map<K,V> {2 3 // ========== 基本操作 ==========4 V put(K key, V value); // 添加或更新键值对5 V get(Object key); // 根据键获取值6 V remove(Object key); // 根据键删除键值对7 boolean containsKey(Object key); // 是否包含指定键8 boolean containsValue(Object value); // 是否包含指定值9 10 // ========== 集合操作 ==========11 Set<K> keySet(); // 获取所有键的集合12 Collection<V> values(); // 获取所有值的集合13 Set<Map.Entry<K,V>> entrySet(); // 获取所有键值对的集合14 15 // ========== 批量操作 ==========16 void putAll(Map<? extends K,? extends V> m); // 添加另一个Map的所有元素17 void clear(); // 清空Map18 19 // ========== 查询操作 ==========20 int size(); // 获取元素个数21 boolean isEmpty(); // 判断是否为空22}1.4 Map接口核心方法详解
Map接口提供了丰富的方法来操作键值对集合,这些方法可以分为几个主要类别:
- 基本操作方法
- 集合视图方法
- 批量操作方法
- Java 8+ 新增方法
1public interface Map<K,V> {2 3 // 添加和更新4 V put(K key, V value); // 添加或更新键值对5 V putIfAbsent(K key, V value); // 如果键不存在则添加6 7 // 获取和查找8 V get(Object key); // 根据键获取值9 V getOrDefault(Object key, V defaultValue); // 获取值,不存在返回默认值10 boolean containsKey(Object key); // 是否包含指定键11 boolean containsValue(Object value); // 是否包含指定值12 13 // 删除操作14 V remove(Object key); // 根据键删除键值对15 boolean remove(Object key, Object value); // 根据键值对删除16}| 方法 | 描述 | 返回值 | 特殊行为 |
|---|---|---|---|
put(K,V) | 添加或更新键值对 | 原值或null | 键已存在时会覆盖原值 |
get(Object) | 获取指定键的值 | 值或null | 键不存在返回null |
remove(Object) | 删除键值对 | 被删除的值或null | 键不存在返回null |
containsKey(Object) | 检查键是否存在 | boolean | HashMap为O(1),TreeMap为O(log n) |
containsValue(Object) | 检查值是否存在 | boolean | 遍历全部元素,性能较差O(n) |
1public interface Map<K,V> {2 3 // 键集合视图4 Set<K> keySet(); // 获取所有键的Set视图5 NavigableSet<K> navigableKeySet(); // 获取可导航的键Set视图(SortedMap)6 7 // 值集合视图8 Collection<V> values(); // 获取所有值的Collection视图9 10 // 键值对集合视图11 Set<Map.Entry<K,V>> entrySet(); // 获取所有键值对的Set视图12}| 方法 | 描述 | 返回视图特性 | 应用场景 |
|---|---|---|---|
keySet() | 获取所有键的Set | 键集合,与原Map同步 | 需要遍历或操作所有键时 |
values() | 获取所有值的集合 | 值集合,与原Map同步 | 需要遍历或访问所有值时 |
entrySet() | 获取所有键值对 | Entry集合,与原Map同步 | 同时需要键和值,效率最高 |
Map返回的集合视图具有以下特性:
- 视图是动态的,对视图的修改会反映到原Map,反之亦然
- 通过视图的
remove()方法可以删除Map中的元素 - 不允许通过键集视图添加元素(会抛出
UnsupportedOperationException) - 视图对象不需要额外内存,它们引用的是同一个底层数据结构
1Map<String, Integer> map = new HashMap<>();2map.put("A", 1);3map.put("B", 2);45// 使用keySet遍历6for (String key : map.keySet()) {7 System.out.println(key); // 输出: A, B8}910// 使用values遍历11for (Integer value : map.values()) {12 System.out.println(value); // 输出: 1, 213}1415// 使用entrySet遍历 (最高效)16for (Map.Entry<String, Integer> entry : map.entrySet()) {17 System.out.println(entry.getKey() + ": " + entry.getValue());18}1public interface Map<K,V> {2 3 // 批量添加4 void putAll(Map<? extends K,? extends V> m); // 添加另一个Map的所有元素5 6 // 批量删除7 void clear(); // 清空Map8 9 // 条件操作10 V replace(K key, V value); // 替换指定键的值11 boolean replace(K key, V oldValue, V newValue); // 条件替换12 void replaceAll(BiFunction<? super K,? super V,? extends V> function); // 批量替换13}| 方法 | 描述 | 返回值 | 性能特征 |
|---|---|---|---|
putAll(Map) | 批量添加键值对 | void | 时间复杂度O(n) |
clear() | 清空所有键值对 | void | 时间复杂度O(n) |
replace(K,V) | 替换指定键的值 | 原值或null | 键不存在时不操作 |
1public interface Map<K,V> {2 3 // Java 8+ 新增方法4 default V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) {5 // 如果键不存在,则计算值并插入6 }7 8 default V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) {9 // 如果键存在,则重新计算值10 }11 12 default V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) {13 // 计算新值(无论键是否存在)14 }15 16 default V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) {17 // 合并现有值与给定值18 }19 20 default void forEach(BiConsumer<? super K,? super V> action) {21 // 对每个键值对执行操作22 }23}| 方法 | 描述 | 使用场景 | 优势 |
|---|---|---|---|
computeIfAbsent | 键不存在时才计算并插入 | 缓存实现 | 避免重复计算 |
compute | 重新计算键的值 | 值依赖于键和旧值 | 原子更新 |
merge | 合并现有值和新值 | 累积统计 | 原子更新 |
forEach | 遍历所有键值对 | 批量处理 | 简洁语法 |
1Map<String, Integer> scores = new HashMap<>();23// computeIfAbsent示例4scores.computeIfAbsent("Alice", key -> calculateScore(key)); // 键不存在时才计算56// 计数器示例7Map<String, Integer> wordCounts = new HashMap<>();8String[] words = {"apple", "banana", "apple", "orange", "banana", "apple"};910for (String word : words) {11 wordCounts.merge(word, 1, (oldValue, value) -> oldValue + value);12}1314// 结果: {orange=1, banana=2, apple=3}1.5 Map接口方法分类对比
| 方法类别 | 主要方法 | 时间复杂度 | 使用场景 |
|---|---|---|---|
| 基本操作 | put(K,V), get(K), remove(K) | O(1) - O(log n) | 日常的增删改查操作 |
| 查找操作 | containsKey(K), containsValue(V) | O(1) - O(n) | 键值存在性检查 |
| 集合视图 | keySet(), values(), entrySet() | O(1) | 遍历和批量操作 |
| 批量操作 | putAll(Map), clear() | O(n) | 批量数据处理 |
- 基本操作的时间复杂度取决于具体实现类
- HashMap的查找、插入、删除为O(1)
- TreeMap的查找、插入、删除为O(log n)
- 选择实现类时应根据性能要求和使用场景
2. Map 实现类详解
- HashMap 实现
- TreeMap 实现
2.1 HashMap 概述
HashMap是基于哈希表实现的Map,是Java中最常用的Map实现类,具有以下特点:
- 🔍 哈希表实现:基于数组+链表+红黑树的数据结构
- 🔀 无序性:不保证元素的顺序
- ⚪ 允许null:允许null键和null值
- ⚠️ 线程不安全:在多线程环境下需要外部同步
- ⚡ 高效操作:查找、插入、删除性能好,平均O(1)
适用场景
- 一般的键值对存储需求
- 需要高效的查找、插入、删除操作
- 对元素顺序没有要求
- 单线程环境或已进行外部同步
2.2 HashMap 内部结构
HashMap基于哈希表实现,内部使用数组+链表+红黑树的数据结构。当链表长度超过阈值时,会转换为红黑树以提高性能。
核心字段
1public class HashMap<K,V> extends AbstractMap<K,V>2 implements Map<K,V>, Cloneable, Serializable {3 4 // 默认初始容量5 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 166 7 // 最大容量8 static final int MAXIMUM_CAPACITY = 1 << 30;9 10 // 默认负载因子11 static final float DEFAULT_LOAD_FACTOR = 0.75f;12 13 // 链表转红黑树的阈值14 static final int TREEIFY_THRESHOLD = 8;15 16 // 红黑树转链表的阈值17 static final int UNTREEIFY_THRESHOLD = 6;18 19 // 最小树化容量20 static final int MIN_TREEIFY_CAPACITY = 64;21 22 // 哈希表23 transient Node<K,V>[] table;24 25 // 键值对集合26 transient Set<Map.Entry<K,V>> entrySet;27 28 // 元素个数29 transient int size;30 31 // 修改次数32 transient int modCount;33 34 // 阈值35 int threshold;36 37 // 负载因子38 final float loadFactor;39}构造方法
1public class HashMap<K,V> extends AbstractMap<K,V> {2 3 /**4 * 构造一个默认初始容量(16)和默认负载因子(0.75)的空HashMap5 */6 public HashMap() {7 this.loadFactor = DEFAULT_LOAD_FACTOR;8 }9 10 /**11 * 构造一个指定初始容量的空HashMap12 */13 public HashMap(int initialCapacity) {14 this(initialCapacity, DEFAULT_LOAD_FACTOR);15 }16 17 /**18 * 构造一个指定初始容量和负载因子的空HashMap19 */20 public HashMap(int initialCapacity, float loadFactor) {21 if (initialCapacity < 0)22 throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);23 if (initialCapacity > MAXIMUM_CAPACITY)24 initialCapacity = MAXIMUM_CAPACITY;25 if (loadFactor <= 0 || Float.isNaN(loadFactor))26 throw new IllegalArgumentException("Illegal load factor: " + loadFactor);27 28 this.loadFactor = loadFactor;29 this.threshold = tableSizeFor(initialCapacity);30 }31 32 /**33 * 构造一个包含指定Map元素的HashMap34 */35 public HashMap(Map<? extends K, ? extends V> m) {36 this.loadFactor = DEFAULT_LOAD_FACTOR;37 putMapEntries(m, false);38 }39}内存布局示意图
1HashMap 实例2┌─────────────────────────────────────────┐3│ table: Node<K,V>[] │4│ ├── [0] → Node("key1", "value1") │5│ ├── [1] → Node("key2", "value2") → Node("key10", "value10")6│ ├── [2] → null │7│ └── [3] → TreeNode("key3", "value3") │8│ size: 4 │9│ threshold: 12 │10│ loadFactor: 0.75 │11└─────────────────────────────────────────┘2.3 HashMap 核心方法实现
2.3.1 哈希算法
HashMap使用高效的哈希算法来计算键的哈希值:
1public class HashMap<K,V> extends AbstractMap<K,V> {2 3 /**4 * 计算键的哈希值5 * 使用高16位异或低16位的方式减少冲突6 */7 static final int hash(Object key) {8 int h;9 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);10 }11 12 /**13 * 计算数组索引14 * 使用位运算替代取模运算,提高性能15 */16 static int indexFor(int h, int length) {17 return h & (length - 1);18 }19 20 /**21 * 计算大于等于给定容量的最小2的幂22 */23 static final int tableSizeFor(int cap) {24 int n = cap - 1;25 n |= n >>> 1;26 n |= n >>> 2;27 n |= n >>> 4;28 n |= n >>> 8;29 n |= n >>> 16;30 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;31 }32}2.3.2 节点结构
HashMap支持两种节点类型:链表节点和红黑树节点:
1public class HashMap<K,V> extends AbstractMap<K,V> {2 3 /**4 * 链表节点5 */6static class Node<K,V> implements Map.Entry<K,V> {7 final int hash;8 final K key;9 V value;10 Node<K,V> next;11 12 Node(int hash, K key, V value, Node<K,V> next) {13 this.hash = hash;14 this.key = key;15 this.value = value;16 this.next = next;17 }18 19 public final K getKey() { return key; }20 public final V getValue() { return value; }21 public final String toString() { return key + "=" + value; }22 23 public final int hashCode() {24 return Objects.hashCode(key) ^ Objects.hashCode(value);25 }26 27 public final V setValue(V newValue) {28 V oldValue = value;29 value = newValue;30 return oldValue;31 }32 33 public final boolean equals(Object o) {34 if (o == this)35 return true;36 if (o instanceof Map.Entry) {37 Map.Entry<?,?> e = (Map.Entry<?,?>)o;38 if (Objects.equals(key, e.getKey()) &&39 Objects.equals(value, e.getValue()))40 return true;41 }42 return false;43 }44 }45 46 /**47 * 红黑树节点(继承自Node)48 */49 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {50 TreeNode<K,V> parent; // 父节点51 TreeNode<K,V> left; // 左子节点52 TreeNode<K,V> right; // 右子节点53 TreeNode<K,V> prev; // 前驱节点54 boolean red; // 颜色标记55 56 TreeNode(int hash, K key, V val, Node<K,V> next) {57 super(hash, key, val, next);58 }59 }60}2.3.3 添加元素
1public class HashMap<K,V> extends AbstractMap<K,V> {2 3 /**4 * 添加或更新键值对5 * 时间复杂度:平均O(1),最坏情况O(n)6 */7 public V put(K key, V value) {8 return putVal(hash(key), key, value, false, true);9 }10 11 /**12 * 核心添加方法13 */14 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {15 Node<K,V>[] tab; Node<K,V> p; int n, i;16 17 // 如果表为空或长度为0,进行初始化18 if ((tab = table) == null || (n = tab.length) == 0)19 n = (tab = resize()).length;20 21 // 计算索引位置22 if ((p = tab[i = (n - 1) & hash]) == null)23 // 如果该位置为空,直接创建新节点24 tab[i] = newNode(hash, key, value, null);25 else {26 Node<K,V> e; K k;27 if (p.hash == hash &&28 ((k = p.key) == key || (key != null && key.equals(k))))29 // 如果键相同,更新值30 e = p;31 else if (p instanceof TreeNode)32 // 如果是红黑树节点,在树中插入33 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);34 else {35 // 在链表中查找或插入36 for (int binCount = 0; ; ++binCount) {37 if ((e = p.next) == null) {38 p.next = newNode(hash, key, value, null);39 if (binCount >= TREEIFY_THRESHOLD - 1)40 treeifyBin(tab, hash);41 break;42 }43 if (e.hash == hash &&44 ((k = e.key) == key || (key != null && key.equals(k))))45 break;46 p = e;47 }48 }49 50 if (e != null) { // 如果找到了相同的键51 V oldValue = e.value;52 if (!onlyIfAbsent || oldValue == null)53 e.value = value;54 afterNodeAccess(e);55 return oldValue;56 }57 }58 59 ++modCount;60 if (++size > threshold)61 resize();62 afterNodeInsertion(evict);63 return null;64 }65}2.3.4 扩容机制
当HashMap的元素个数超过阈值时,会自动扩容:
1public class HashMap<K,V> extends AbstractMap<K,V> {2 3 /**4 * 扩容方法5 */6 final Node<K,V>[] resize() {7 Node<K,V>[] oldTab = table;8 int oldCap = (oldTab == null) ? 0 : oldTab.length;9 int oldThr = threshold;10 int newCap, newThr = 0;11 12 if (oldCap > 0) {13 if (oldCap >= MAXIMUM_CAPACITY) {14 threshold = Integer.MAX_VALUE;15 return oldTab;16 }17 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&18 oldCap >= DEFAULT_INITIAL_CAPACITY)19 newThr = oldThr << 1; // 双倍扩容20 }21 else if (oldThr > 0)22 newCap = oldThr;23 else {24 newCap = DEFAULT_INITIAL_CAPACITY;25 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);26 }27 28 if (newThr == 0) {29 float ft = (float)newCap * loadFactor;30 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?31 (int)ft : Integer.MAX_VALUE);32 }33 34 threshold = newThr;35 @SuppressWarnings({"rawtypes","unchecked"})36 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];37 table = newTab;38 39 if (oldTab != null) {40 // 重新哈希所有元素41 for (int j = 0; j < oldCap; ++j) {42 Node<K,V> e;43 if ((e = oldTab[j]) != null) {44 oldTab[j] = null;45 if (e.next == null)46 newTab[e.hash & (newCap - 1)] = e;47 else if (e instanceof TreeNode)48 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);49 else {50 Node<K,V> loHead = null, loTail = null;51 Node<K,V> hiHead = null, hiTail = null;52 Node<K,V> next;53 do {54 next = e.next;55 if ((e.hash & oldCap) == 0) {56 if (loTail == null)57 loHead = e;58 else59 loTail.next = e;60 loTail = e;61 }62 else {63 if (hiTail == null)64 hiHead = e;65 else66 hiTail.next = e;67 hiTail = e;68 }69 } while ((e = next) != null);70 if (loTail != null) {71 loTail.next = null;72 newTab[j] = loHead;73 }74 if (hiTail != null) {75 hiTail.next = null;76 newTab[j + oldCap] = hiHead;77 }78 }79 }80 }81 }82 return newTab;83 }84}- 默认初始容量:16
- 扩容触发:当元素个数超过threshold时
- 扩容倍数:2倍(newCap = oldCap << 1)
- 阈值计算:threshold = capacity * loadFactor
- 扩容开销:需要重新哈希所有元素,开销较大
2.4 HashMap 性能分析
2.4.1 时间复杂度对比
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 查找 | 平均O(1) | 基于哈希值直接定位 |
| 插入 | 平均O(1) | 直接插入或更新 |
| 删除 | 平均O(1) | 直接删除节点 |
| 遍历 | O(n) | 需要遍历所有元素 |
2.4.2 空间复杂度
- 存储开销:每个键值对需要一个Node对象
- 数组开销:哈希表数组的空间开销
- 链表开销:冲突时链表的额外空间
- 红黑树开销:树化时的额外空间
2.4.3 性能优化建议
1public class HashMapOptimization {2 3 public static void main(String[] args) {4 // 1. 预分配容量,避免频繁扩容5 Map<String, Integer> map1 = new HashMap<>(1000);6 7 // 2. 选择合适的负载因子8 Map<String, Integer> map2 = new HashMap<>(16, 0.5f); // 降低负载因子9 10 // 3. 使用合适的键类型11 Map<String, Integer> goodKeys = new HashMap<>(); // 推荐:不可变对象12 // Map<StringBuilder, Integer> badKeys = new HashMap<>(); // 不推荐:可变对象13 14 // 4. 批量操作15 Map<String, Integer> sourceMap = new HashMap<>();16 sourceMap.put("A", 1);17 sourceMap.put("B", 2);18 19 Map<String, Integer> targetMap = new HashMap<>();20 targetMap.putAll(sourceMap); // 批量添加21 22 // 5. 使用getOrDefault避免null检查23 Integer value = targetMap.getOrDefault("C", 0);24 25 // 6. 使用computeIfAbsent延迟初始化26 targetMap.computeIfAbsent("D", k -> expensiveOperation(k));27 }28 29 private static Integer expensiveOperation(String key) {30 // 模拟昂贵的操作31 return key.length() * 10;32 }33}2.5 HashMap 使用示例
2.5.1 基本操作示例
1public class HashMapBasicExample {2 3 public static void main(String[] args) {4 // 创建HashMap5 Map<String, Integer> map = new HashMap<>();6 7 // 添加元素8 map.put("Java", 1);9 map.put("Python", 2);10 map.put("C++", 3);11 12 // 获取元素13 Integer value = map.get("Java");14 System.out.println("Java的值: " + value);15 16 // 检查键是否存在17 boolean containsKey = map.containsKey("Java");18 System.out.println("是否包含Java: " + containsKey);19 20 // 检查值是否存在21 boolean containsValue = map.containsValue(2);22 System.out.println("是否包含值2: " + containsValue);23 24 // 删除元素25 map.remove("C++");26 27 // 遍历键值对28 for (Map.Entry<String, Integer> entry : map.entrySet()) {29 System.out.println(entry.getKey() + ": " + entry.getValue());30 }31 32 // 遍历键33 for (String key : map.keySet()) {34 System.out.println("键: " + key);35 }36 37 // 遍历值38 for (Integer val : map.values()) {39 System.out.println("值: " + val);40 }41 }42}2.5.2 高级操作示例
1public class HashMapAdvancedExample {2 3 public static void main(String[] args) {4 Map<String, Integer> map = new HashMap<>();5 6 // 批量添加7 map.putAll(Map.of("A", 1, "B", 2, "C", 3));8 9 // 条件操作10 map.putIfAbsent("A", 10); // 如果键不存在则添加11 map.replace("B", 2, 20); // 条件替换12 map.remove("C", 3); // 条件删除13 14 // 计算操作15 map.compute("D", (k, v) -> (v == null) ? 1 : v + 1);16 map.computeIfAbsent("E", k -> k.length());17 map.computeIfPresent("A", (k, v) -> v * 2);18 19 // 合并操作20 map.merge("F", 1, (oldValue, newValue) -> oldValue + newValue);21 22 // 批量替换23 map.replaceAll((k, v) -> v * 10);24 25 System.out.println("操作后的Map: " + map);26 }27} 2829</TabItem>30<TabItem value="linkedhashmap" label="LinkedHashMap 实现">3132### 3.1 LinkedHashMap 概述3334:::tip[核心特点]35LinkedHashMap是HashMap的子类,它在HashMap的基础上维护了一个双向链表,具有以下特点:36- 🔄 **继承HashMap**:拥有HashMap的所有特性37- 📝 **维护顺序**:维护插入顺序或访问顺序38- 🔗 **双向链表**:通过双向链表维护元素顺序39- 🗄️ **LRU缓存**:可以实现LRU(最近最少使用)缓存40- 📉 **性能略低**:相比HashMap有轻微的性能开销41:::4243#### 适用场景44- 需要保持元素插入顺序的场景45- 需要实现LRU缓存的场景46- 需要按访问顺序排序的场景47- 对顺序有要求但不需要排序的场景4849### 3.2 LinkedHashMap 内部结构5051LinkedHashMap在HashMap的基础上增加了双向链表来维护元素顺序:5253```java title="LinkedHashMap核心字段"54public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {55 56 // 双向链表节点57 static class Entry<K,V> extends HashMap.Node<K,V> {58 Entry<K,V> before, after; // 前驱和后继节点59 Entry(int hash, K key, V value, Node<K,V> next) {60 super(hash, key, value, next);61 }62 }63 64 // 头节点65 transient LinkedHashMap.Entry<K,V> head;66 67 // 尾节点68 transient LinkedHashMap.Entry<K,V> tail;69 70 // 是否按访问顺序排序71 final boolean accessOrder;72}内存布局示意图
1LinkedHashMap 实例2┌─────────────────────────────────────────┐3│ HashMap部分 │4│ table: Node<K,V>[] │5│ ├── [0] → Entry("key1", "value1") │6│ ├── [1] → Entry("key2", "value2") │7│ └── [2] → null │8│ │9│ 双向链表部分 │10│ head → Entry1 ←→ Entry2 ←→ Entry3 ← tail│11│ │ │ │ │12│ before before before │13│ after after after │14│ │15│ accessOrder: false │16└─────────────────────────────────────────┘3.3 LinkedHashMap 核心方法实现
3.3.1 节点链接
1public class LinkedHashMap<K,V> extends HashMap<K,V> {2 3 /**4 * 在链表头部添加节点5 */6 private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {7 LinkedHashMap.Entry<K,V> last = tail;8 tail = p;9 if (last == null)10 head = p;11 else {12 p.before = last;13 last.after = p;14 }15 }16 17 /**18 * 在链表头部添加节点19 */20 private void linkNodeFirst(LinkedHashMap.Entry<K,V> p) {21 LinkedHashMap.Entry<K,V> first = head;22 head = p;23 if (first == null)24 tail = p;25 else {26 p.after = first;27 first.before = p;28 }29 }30 31 /**32 * 从链表中移除节点33 */34 private void unlinkNode(LinkedHashMap.Entry<K,V> p) {35 LinkedHashMap.Entry<K,V> before = p.before;36 LinkedHashMap.Entry<K,V> after = p.after;37 38 if (before == null)39 head = after;40 else {41 before.after = after;42 p.before = null;43 }44 45 if (after == null)46 tail = before;47 else {48 after.before = before;49 p.after = null;50 }51 }52}3.3.2 访问顺序维护
1public class LinkedHashMap<K,V> extends HashMap<K,V> {2 3 /**4 * 访问节点后的处理5 */6 void afterNodeAccess(Node<K,V> e) {7 LinkedHashMap.Entry<K,V> last;8 if (accessOrder && (last = tail) != e) {9 LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e;10 LinkedHashMap.Entry<K,V> b = p.before;11 LinkedHashMap.Entry<K,V> a = p.after;12 p.after = null;13 if (b == null)14 head = a;15 else16 b.after = a;17 if (a != null)18 a.before = b;19 else20 last = b;21 if (last == null)22 head = p;23 else {24 p.before = last;25 last.after = p;26 }27 tail = p;28 ++modCount;29 }30 }31 32 /**33 * 插入节点后的处理34 */35 void afterNodeInsertion(boolean evict) {36 LinkedHashMap.Entry<K,V> first;37 if (evict && (first = head) != null && removeEldestEntry(first)) {38 K key = first.key;39 removeNode(hash(key), key, null, false, true);40 }41 }42 43 /**44 * 是否移除最老的节点(用于LRU缓存)45 */46 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {47 return false; // 默认不移除48 }49}3.4 LinkedHashMap 使用示例
3.4.1 基本使用示例
1public class LinkedHashMapBasicExample {2 3 public static void main(String[] args) {4 // 按插入顺序排序5 Map<String, Integer> insertOrderMap = new LinkedHashMap<>();6 insertOrderMap.put("Java", 1);7 insertOrderMap.put("Python", 2);8 insertOrderMap.put("C++", 3);9 10 System.out.println("按插入顺序:");11 for (Map.Entry<String, Integer> entry : insertOrderMap.entrySet()) {12 System.out.println(entry.getKey() + ": " + entry.getValue());13 }14 15 // 按访问顺序排序16 Map<String, Integer> accessOrderMap = new LinkedHashMap<>(16, 0.75f, true);17 accessOrderMap.put("Java", 1);18 accessOrderMap.put("Python", 2);19 accessOrderMap.put("C++", 3);20 21 // 访问元素,会改变顺序22 accessOrderMap.get("Java");23 accessOrderMap.get("Python");24 25 System.out.println("按访问顺序:");26 for (Map.Entry<String, Integer> entry : accessOrderMap.entrySet()) {27 System.out.println(entry.getKey() + ": " + entry.getValue());28 }29 }30}3.4.2 LRU缓存实现
1public class LRUCache<K, V> extends LinkedHashMap<K, V> {2 private final int capacity;3 4 public LRUCache(int capacity) {5 super(capacity, 0.75f, true); // 按访问顺序排序6 this.capacity = capacity;7 }8 9 @Override10 protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {11 return size() > capacity; // 超过容量时移除最老的元素12 }13 14 public static void main(String[] args) {15 LRUCache<String, Integer> cache = new LRUCache<>(3);16 17 cache.put("A", 1);18 cache.put("B", 2);19 cache.put("C", 3);20 21 System.out.println("初始缓存: " + cache);22 23 cache.get("A"); // 访问A,A变为最新24 cache.put("D", 4); // 添加D,B被移除(最老的)25 26 System.out.println("访问A后添加D: " + cache);27 }28}4.1 TreeMap 概述
TreeMap是基于红黑树实现的Map,具有以下特点:
- 🌳 红黑树实现:基于红黑树的有序数据结构
- 📊 有序性:按键的自然顺序或自定义顺序排序
- ⛔ 不允许null键:键不能为null,值可以为null
- ⚠️ 线程不安全:在多线程环境下需要外部同步
- 📈 性能稳定:查找、插入、删除性能为O(log n)
适用场景
- 需要按键排序的场景
- 需要范围查询的场景
- 需要获取最大最小键的场景
- 对性能稳定性要求较高的场景
4.2 TreeMap 内部结构
TreeMap基于红黑树实现,每个节点包含键值对和左右子节点:
1public class TreeMap<K,V> extends AbstractMap<K,V>2 implements NavigableMap<K,V>, Cloneable, java.io.Serializable {3 4 // 比较器5 private final Comparator<? super K> comparator;6 7 // 根节点8 private transient Entry<K,V> root;9 10 // 元素个数11 private transient int size = 0;12 13 // 修改次数14 private transient int modCount = 0;15 16 // 红黑树节点17 static final class Entry<K,V> implements Map.Entry<K,V> {18 K key;19 V value;20 Entry<K,V> left;21 Entry<K,V> right;22 Entry<K,V> parent;23 boolean color = BLACK;24 25 Entry(K key, V value, Entry<K,V> parent) {26 this.key = key;27 this.value = value;28 this.parent = parent;29 }30 }31}4.3 TreeMap 核心方法实现
4.3.1 查找操作
1public class TreeMap<K,V> extends AbstractMap<K,V> {2 3 /**4 * 根据键获取值5 * 时间复杂度:O(log n)6 */7 public V get(Object key) {8 Entry<K,V> p = getEntry(key);9 return (p == null ? null : p.value);10 }11 12 /**13 * 查找指定键的节点14 */15 final Entry<K,V> getEntry(Object key) {16 if (comparator != null)17 return getEntryUsingComparator(key);18 if (key == null)19 throw new NullPointerException();20 @SuppressWarnings("unchecked")21 Comparable<? super K> k = (Comparable<? super K>) key;22 Entry<K,V> p = root;23 while (p != null) {24 int cmp = k.compareTo(p.key);25 if (cmp < 0)26 p = p.left;27 else if (cmp > 0)28 p = p.right;29 else30 return p;31 }32 return null;33 }34 35 /**36 * 使用比较器查找37 */38 final Entry<K,V> getEntryUsingComparator(Object key) {39 @SuppressWarnings("unchecked")40 K k = (K) key;41 Comparator<? super K> cpr = comparator;42 if (cpr != null) {43 Entry<K,V> p = root;44 while (p != null) {45 int cmp = cpr.compare(k, p.key);46 if (cmp < 0)47 p = p.left;48 else if (cmp > 0)49 p = p.right;50 else51 return p;52 }53 }54 return null;55 }56}4.3.2 插入操作
1public class TreeMap<K,V> extends AbstractMap<K,V> {2 3 /**4 * 添加或更新键值对5 * 时间复杂度:O(log n)6 */7 public V put(K key, V value) {8 Entry<K,V> t = root;9 if (t == null) {10 compare(key, key); // 类型检查11 root = new Entry<>(key, value, null);12 size = 1;13 modCount++;14 return null;15 }16 int cmp;17 Entry<K,V> parent;18 Comparator<? super K> cpr = comparator;19 if (cpr != null) {20 do {21 parent = t;22 cmp = cpr.compare(key, t.key);23 if (cmp < 0)24 t = t.left;25 else if (cmp > 0)26 t = t.right;27 else28 return t.setValue(value);29 } while (t != null);30 } else {31 if (key == null)32 throw new NullPointerException();33 @SuppressWarnings("unchecked")34 Comparable<? super K> k = (Comparable<? super K>) key;35 do {36 parent = t;37 cmp = k.compareTo(t.key);38 if (cmp < 0)39 t = t.left;40 else if (cmp > 0)41 t = t.right;42 else43 return t.setValue(value);44 } while (t != null);45 }46 Entry<K,V> e = new Entry<>(key, value, parent);47 if (cmp < 0)48 parent.left = e;49 else50 parent.right = e;51 fixAfterInsertion(e);52 size++;53 modCount++;54 return null;55 }56}4.4 TreeMap 使用示例
4.4.1 基本使用示例
1public class TreeMapBasicExample {2 3 public static void main(String[] args) {4 // 自然顺序排序5 Map<String, Integer> naturalOrderMap = new TreeMap<>();6 naturalOrderMap.put("Java", 1);7 naturalOrderMap.put("Python", 2);8 naturalOrderMap.put("C++", 3);9 10 System.out.println("自然顺序:");11 for (Map.Entry<String, Integer> entry : naturalOrderMap.entrySet()) {12 System.out.println(entry.getKey() + ": " + entry.getValue());13 }14 15 // 自定义比较器16 Map<String, Integer> customOrderMap = new TreeMap<>((s1, s2) -> s2.compareTo(s1));17 customOrderMap.put("Java", 1);18 customOrderMap.put("Python", 2);19 customOrderMap.put("C++", 3);20 21 System.out.println("自定义顺序:");22 for (Map.Entry<String, Integer> entry : customOrderMap.entrySet()) {23 System.out.println(entry.getKey() + ": " + entry.getValue());24 }25 }26}4.4.2 导航方法示例
1public class TreeMapNavigationExample {2 3 public static void main(String[] args) {4 TreeMap<String, Integer> treeMap = new TreeMap<>();5 treeMap.put("A", 1);6 treeMap.put("B", 2);7 treeMap.put("C", 3);8 treeMap.put("D", 4);9 treeMap.put("E", 5);10 11 // 获取第一个元素12 Map.Entry<String, Integer> first = treeMap.firstEntry();13 System.out.println("第一个元素: " + first.getKey() + "=" + first.getValue());14 15 // 获取最后一个元素16 Map.Entry<String, Integer> last = treeMap.lastEntry();17 System.out.println("最后一个元素: " + last.getKey() + "=" + last.getValue());18 19 // 获取小于指定键的最大键值对20 Map.Entry<String, Integer> lower = treeMap.lowerEntry("C");21 System.out.println("小于C的最大元素: " + lower.getKey() + "=" + lower.getValue());22 23 // 获取大于指定键的最小键值对24 Map.Entry<String, Integer> higher = treeMap.higherEntry("B");25 System.out.println("大于B的最小元素: " + higher.getKey() + "=" + higher.getValue());26 27 // 范围查询28 SortedMap<String, Integer> subMap = treeMap.subMap("B", "D");29 System.out.println("B到D的子Map: " + subMap);30 }31}5. ConcurrentHashMap 实现类详解
5.1 ConcurrentHashMap 概述
ConcurrentHashMap是线程安全的HashMap实现,具有以下特点:
- 线程安全:支持多线程并发访问
- 分段锁机制:使用分段锁减少锁竞争
- 不允许null:键和值都不能为null
- 高性能:性能优于Hashtable和Collections.synchronizedMap
- 原子操作:提供原子操作方法
适用场景
- 多线程环境下的Map操作
- 高并发场景下的缓存实现
- 需要线程安全的键值对存储
- 对性能要求较高的并发应用
5.2 ConcurrentHashMap 内部结构
ConcurrentHashMap使用分段锁机制,将整个Map分成多个段,每个段使用独立的锁:
1public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>2 implements ConcurrentMap<K,V>, Serializable {3 4 // 段数组5 final Segment<K,V>[] segments;6 7 // 段的数量8 static final int DEFAULT_CONCURRENCY_LEVEL = 16;9 10 // 段类11 static final class Segment<K,V> extends ReentrantLock implements Serializable {12 transient volatile HashEntry<K,V>[] table;13 transient int count;14 transient int modCount;15 transient int threshold;16 final float loadFactor;17 }18 19 // 哈希表节点20 static final class HashEntry<K,V> {21 final K key;22 final int hash;23 volatile V value;24 final HashEntry<K,V> next;25 26 HashEntry(K key, int hash, HashEntry<K,V> next, V value) {27 this.key = key;28 this.hash = hash;29 this.next = next;30 this.value = value;31 }32 }33}5.3 ConcurrentHashMap 使用示例
5.3.1 基本使用示例
1public class ConcurrentHashMapBasicExample {2 3 public static void main(String[] args) {4 Map<String, Integer> map = new ConcurrentHashMap<>();5 6 // 多线程安全操作7 Thread t1 = new Thread(() -> {8 for (int i = 0; i < 1000; i++) {9 map.put("Thread1-" + i, i);10 }11 });12 13 Thread t2 = new Thread(() -> {14 for (int i = 0; i < 1000; i++) {15 map.put("Thread2-" + i, i);16 }17 });18 19 t1.start();20 t2.start();21 22 try {23 t1.join();24 t2.join();25 } catch (InterruptedException e) {26 e.printStackTrace();27 }28 29 System.out.println("最终大小: " + map.size());30 }31}5.3.2 原子操作示例
1public class ConcurrentHashMapAtomicExample {2 3 public static void main(String[] args) {4 ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();5 6 // 原子操作7 map.putIfAbsent("key", 1); // 如果键不存在则添加8 map.replace("key", 1, 2); // 如果值等于1则替换为29 map.remove("key", 2); // 如果值等于2则删除10 11 // 原子更新12 map.put("counter", 0);13 map.compute("counter", (k, v) -> v + 1);14 map.merge("counter", 1, Integer::sum);15 16 System.out.println("最终结果: " + map);17 }18} 1920## 6. 其他Map实现2122### 6.1 Hashtable2324Hashtable是Java早期提供的线程安全的Map实现,现在已经不推荐使用:2526```java title="Hashtable示例"27public class HashtableExample {28 29 public static void main(String[] args) {30 Map<String, Integer> map = new Hashtable<>();31 32 // 基本操作33 map.put("Java", 1);34 map.put("Python", 2);35 36 // 不允许null键和null值37 // map.put(null, 1); // 抛出NullPointerException38 // map.put("key", null); // 抛出NullPointerException39 40 System.out.println(map);41 42 // 性能对比43 long start = System.currentTimeMillis();44 for (int i = 0; i < 100000; i++) {45 map.put("key" + i, i);46 }47 long end = System.currentTimeMillis();48 System.out.println("Hashtable插入100000个元素耗时: " + (end - start) + "ms");49 }50}6.2 WeakHashMap
WeakHashMap使用弱引用作为键,当键不再被强引用时,对应的键值对会被自动回收:
1public class WeakHashMapExample {2 3 public static void main(String[] args) {4 Map<String, Integer> map = new WeakHashMap<>();5 6 String key1 = new String("key1");7 String key2 = new String("key2");8 9 map.put(key1, 1);10 map.put(key2, 2);11 12 System.out.println("GC前: " + map.size()); // 213 14 // 释放强引用15 key1 = null;16 key2 = null;17 18 // 触发GC19 System.gc();20 21 System.out.println("GC后: " + map.size()); // 022 }23}6.3 IdentityHashMap
IdentityHashMap使用==而不是equals()来比较键,适用于需要精确对象引用的场景:
1public class IdentityHashMapExample {2 3 public static void main(String[] args) {4 Map<String, Integer> normalMap = new HashMap<>();5 Map<String, Integer> identityMap = new IdentityHashMap<>();6 7 String key1 = new String("key");8 String key2 = new String("key");9 10 // HashMap使用equals()比较11 normalMap.put(key1, 1);12 normalMap.put(key2, 2);13 System.out.println("HashMap大小: " + normalMap.size()); // 114 15 // IdentityHashMap使用==比较16 identityMap.put(key1, 1);17 identityMap.put(key2, 2);18 System.out.println("IdentityHashMap大小: " + identityMap.size()); // 219 }20}7. 实际应用场景
7.1 缓存系统
1public class CacheSystemExample {2 3 /**4 * 简单的内存缓存实现5 */6 public static class SimpleCache<K, V> {7 private final Map<K, V> cache = new ConcurrentHashMap<>();8 private final Map<K, Long> timestamps = new ConcurrentHashMap<>();9 private final long ttl; // 生存时间10 11 public SimpleCache(long ttl) {12 this.ttl = ttl;13 }14 15 public V get(K key) {16 V value = cache.get(key);17 if (value != null) {18 Long timestamp = timestamps.get(key);19 if (timestamp != null && System.currentTimeMillis() - timestamp < ttl) {20 return value;21 } else {22 // 过期,删除23 cache.remove(key);24 timestamps.remove(key);25 }26 }27 return null;28 }29 30 public void put(K key, V value) {31 cache.put(key, value);32 timestamps.put(key, System.currentTimeMillis());33 }34 35 public void clear() {36 cache.clear();37 timestamps.clear();38 }39 }40 41 public static void main(String[] args) {42 SimpleCache<String, String> cache = new SimpleCache<>(5000); // 5秒TTL43 44 cache.put("user:1", "Alice");45 cache.put("user:2", "Bob");46 47 System.out.println("获取用户1: " + cache.get("user:1"));48 49 // 等待6秒后,缓存过期50 try {51 Thread.sleep(6000);52 } catch (InterruptedException e) {53 e.printStackTrace();54 }55 56 System.out.println("6秒后获取用户1: " + cache.get("user:1")); // null57 }58}7.2 配置管理
1public class ConfigManagementExample {2 3 /**4 * 配置管理器5 */6 public static class ConfigManager {7 private final Map<String, Object> config = new ConcurrentHashMap<>();8 private final Map<String, List<ConfigChangeListener>> listeners = new ConcurrentHashMap<>();9 10 public void setConfig(String key, Object value) {11 Object oldValue = config.put(key, value);12 notifyListeners(key, oldValue, value);13 }14 15 public Object getConfig(String key) {16 return config.get(key);17 }18 19 public <T> T getConfig(String key, Class<T> type) {20 Object value = config.get(key);21 return type.isInstance(value) ? type.cast(value) : null;22 }23 24 public void addListener(String key, ConfigChangeListener listener) {25 listeners.computeIfAbsent(key, k -> new ArrayList<>()).add(listener);26 }27 28 private void notifyListeners(String key, Object oldValue, Object newValue) {29 List<ConfigChangeListener> keyListeners = listeners.get(key);30 if (keyListeners != null) {31 for (ConfigChangeListener listener : keyListeners) {32 listener.onConfigChange(key, oldValue, newValue);33 }34 }35 }36 }37 38 @FunctionalInterface39 interface ConfigChangeListener {40 void onConfigChange(String key, Object oldValue, Object newValue);41 }42 43 public static void main(String[] args) {44 ConfigManager configManager = new ConfigManager();45 46 // 添加配置变更监听器47 configManager.addListener("database.url", (key, oldValue, newValue) -> {48 System.out.println("数据库URL变更: " + oldValue + " -> " + newValue);49 });50 51 // 设置配置52 configManager.setConfig("database.url", "jdbc:mysql://localhost:3306/test");53 configManager.setConfig("database.url", "jdbc:mysql://localhost:3306/prod");54 55 // 获取配置56 String dbUrl = configManager.getConfig("database.url", String.class);57 System.out.println("当前数据库URL: " + dbUrl);58 }59}7.3 数据统计
1public class DataStatisticsExample {2 3 /**4 * 用户行为统计5 */6 public static class UserBehaviorStats {7 private final Map<String, Integer> pageViews = new ConcurrentHashMap<>();8 private final Map<String, Set<String>> uniqueUsers = new ConcurrentHashMap<>();9 private final Map<String, List<Long>> accessTimes = new ConcurrentHashMap<>();10 11 public void recordPageView(String page, String userId) {12 // 页面访问次数统计13 pageViews.merge(page, 1, Integer::sum);14 15 // 独立用户统计16 uniqueUsers.computeIfAbsent(page, k -> ConcurrentHashMap.newKeySet()).add(userId);17 18 // 访问时间记录19 accessTimes.computeIfAbsent(page, k -> new ArrayList<>()).add(System.currentTimeMillis());20 }21 22 public int getPageViews(String page) {23 return pageViews.getOrDefault(page, 0);24 }25 26 public int getUniqueUsers(String page) {27 Set<String> users = uniqueUsers.get(page);28 return users != null ? users.size() : 0;29 }30 31 public double getAverageAccessTime(String page) {32 List<Long> times = accessTimes.get(page);33 if (times == null || times.isEmpty()) {34 return 0.0;35 }36 return times.stream().mapToLong(Long::longValue).average().orElse(0.0);37 }38 39 public Map<String, Integer> getTopPages(int limit) {40 return pageViews.entrySet().stream()41 .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())42 .limit(limit)43 .collect(Collectors.toMap(44 Map.Entry::getKey,45 Map.Entry::getValue,46 (e1, e2) -> e1,47 LinkedHashMap::new48 ));49 }50 }51 52 public static void main(String[] args) {53 UserBehaviorStats stats = new UserBehaviorStats();54 55 // 模拟用户访问56 stats.recordPageView("home", "user1");57 stats.recordPageView("home", "user2");58 stats.recordPageView("home", "user1");59 stats.recordPageView("products", "user1");60 stats.recordPageView("products", "user3");61 62 System.out.println("首页访问次数: " + stats.getPageViews("home"));63 System.out.println("首页独立用户: " + stats.getUniqueUsers("home"));64 System.out.println("热门页面: " + stats.getTopPages(3));65 }66}8. 最佳实践总结
8.1 Map实现类选择策略
选择合适的Map实现类需要考虑以下因素:
- 性能要求:HashMap性能最好,TreeMap性能稳定
- 顺序要求:需要顺序选择LinkedHashMap或TreeMap
- 线程安全:多线程环境选择ConcurrentHashMap
- 内存管理:缓存场景考虑WeakHashMap
- 特殊需求:根据具体业务需求选择
| 实现类 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| HashMap | 一般用途 | 性能最好、功能完整 | 无序、线程不安全 |
| LinkedHashMap | 需要保持顺序 | 维护插入/访问顺序 | 性能略低于HashMap |
| TreeMap | 需要排序 | 有序、支持范围查询 | 性能O(log n) |
| ConcurrentHashMap | 多线程环境 | 线程安全、高性能 | 不允许null键值 |
| WeakHashMap | 缓存场景 | 自动回收、内存友好 | 键可能被回收 |
| Hashtable | 不推荐使用 | 线程安全 | 性能差、已过时 |
8.2 性能优化策略
1public class MapPerformanceOptimization {2 3 /**4 * 容量优化5 */6 public static void capacityOptimization() {7 // 预分配容量,避免频繁扩容8 Map<String, Integer> optimizedMap = new HashMap<>(1000);9 10 // 选择合适的负载因子11 Map<String, Integer> lowLoadFactorMap = new HashMap<>(16, 0.5f);12 }13 14 /**15 * 键类型优化16 */17 public static void keyTypeOptimization() {18 // 使用不可变对象作为键19 Map<String, Integer> goodKeys = new HashMap<>();20 21 // 避免使用可变对象作为键22 // Map<StringBuilder, Integer> badKeys = new HashMap<>();23 }24 25 /**26 * 批量操作优化27 */28 public static void batchOperationOptimization() {29 Map<String, Integer> sourceMap = new HashMap<>();30 sourceMap.put("A", 1);31 sourceMap.put("B", 2);32 33 Map<String, Integer> targetMap = new HashMap<>();34 targetMap.putAll(sourceMap); // 批量添加35 36 // 批量删除37 targetMap.keySet().removeAll(Arrays.asList("A", "B"));38 }39 40 /**41 * 原子操作优化42 */43 public static void atomicOperationOptimization() {44 ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();45 46 // 使用原子操作避免竞态条件47 map.putIfAbsent("key", 1);48 map.replace("key", 1, 2);49 map.compute("counter", (k, v) -> (v == null) ? 1 : v + 1);50 }51}8.3 常见陷阱和解决方案
- HashMap的线程安全问题:多线程环境下需要外部同步
- TreeMap的null键限制:键不能为null,需要特殊处理
- ConcurrentHashMap的null限制:键和值都不能为null
- WeakHashMap的键回收:键可能被垃圾回收,需要谨慎使用
1public class MapCommonTraps {2 3 /**4 * 线程安全问题5 */6 public static void threadSafetyTrap() {7 Map<String, Integer> map = new HashMap<>();8 9 // 错误:多线程环境下直接使用HashMap10 Thread t1 = new Thread(() -> {11 for (int i = 0; i < 1000; i++) {12 map.put("key" + i, i);13 }14 });15 16 Thread t2 = new Thread(() -> {17 for (int i = 0; i < 1000; i++) {18 map.put("key" + i, i);19 }20 });21 22 // 正确:使用ConcurrentHashMap23 Map<String, Integer> safeMap = new ConcurrentHashMap<>();24 }25 26 /**27 * null值处理28 */29 public static void nullValueTrap() {30 // TreeMap不允许null键31 try {32 TreeMap<String, Integer> treeMap = new TreeMap<>();33 treeMap.put(null, 1); // 抛出NullPointerException34 } catch (Exception e) {35 System.out.println("TreeMap不允许null键: " + e.getMessage());36 }37 38 // ConcurrentHashMap不允许null键和null值39 try {40 ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();41 concurrentMap.put("key", null); // 抛出NullPointerException42 } catch (Exception e) {43 System.out.println("ConcurrentHashMap不允许null值: " + e.getMessage());44 }45 }46 47 /**48 * 键的不可变性49 */50 public static void keyImmutabilityTrap() {51 Map<StringBuilder, Integer> badMap = new HashMap<>();52 StringBuilder key = new StringBuilder("key");53 badMap.put(key, 1);54 55 // 修改键的内容56 key.append("Modified");57 58 // 现在无法通过原键获取值59 System.out.println("修改键后获取值: " + badMap.get(new StringBuilder("key"))); // null60 System.out.println("修改后的键获取值: " + badMap.get(key)); // 161 }62}9. 总结
Map接口作为Java集合框架的重要组成部分,提供了键值对存储的核心功能。通过深入理解HashMap、LinkedHashMap、TreeMap和ConcurrentHashMap的实现原理,我们可以根据具体的使用场景选择最合适的实现类,并通过最佳实践来优化性能、避免常见陷阱。
在实际开发中,需要综合考虑以下几个方面:
- 性能要求:根据访问模式选择合适的数据结构
- 顺序要求:根据业务需求选择有序或无序的实现
- 线程安全:在多线程环境下选择合适的实现
- 内存管理:考虑内存使用和垃圾回收的影响
- 特殊需求:根据具体业务场景选择特殊实现
通过合理使用Map集合,我们可以构建出高效、可靠的Java应用程序。
10. 面试题精选
10.1 基础概念题
Q: HashMap和Hashtable的区别是什么?
A: 主要区别包括:
- 线程安全:HashMap线程不安全,Hashtable线程安全(同步)
- null值处理:HashMap允许null键和null值,Hashtable不允许
- 性能:HashMap性能更好,Hashtable性能较差
- 继承关系:HashMap继承AbstractMap,Hashtable继承Dictionary
- 推荐使用:HashMap是推荐选择,Hashtable已过时
Q: HashMap的工作原理是怎样的?
A: HashMap工作原理:
- 哈希算法:使用
hashCode()和位运算计算哈希值 - 索引计算:
hash & (length - 1)计算数组索引 - 冲突处理:链表法处理哈希冲突,链表长度超过8时转为红黑树
- 扩容机制:负载因子超过阈值时进行2倍扩容
- 重新哈希:扩容时重新计算所有元素的索引
10.2 性能优化题
Q: 如何优化HashMap的性能?
A: 主要优化策略:
- 预分配容量:使用带初始容量的构造函数避免频繁扩容
- 合适的负载因子:根据内存和性能需求调整负载因子
- 合适的键类型:使用不可变对象作为键,避免键被修改
- 批量操作:使用
putAll()等方法进行批量操作 - 避免频繁扩容:合理预估元素数量,设置合适的初始容量
Q: 什么场景下选择TreeMap?
A: TreeMap适用场景:
- 需要按键排序的场景
- 需要范围查询的场景
- 需要获取最大最小键的场景
- 对性能稳定性要求较高的场景
- 需要自定义排序规则的场景
10.3 实践应用题
Q: 如何实现线程安全的Map操作?
A: 线程安全方案:
- ConcurrentHashMap:推荐的多线程环境下的Map实现
- Collections.synchronizedMap():包装现有Map提供同步
- Hashtable:不推荐,性能较差
- 外部同步:使用synchronized或Lock进行外部同步
- 原子操作:使用ConcurrentHashMap的原子操作方法
Q: 如何实现LRU缓存?
A: LRU缓存实现方案:
- LinkedHashMap:继承LinkedHashMap,重写
removeEldestEntry()方法 - 自定义实现:使用HashMap + 双向链表实现
- 第三方库:使用Guava Cache或Caffeine等缓存库
- Redis:使用Redis的LRU淘汰策略
10.4 高级特性题
Q: ConcurrentHashMap的线程安全机制是什么?
A: ConcurrentHashMap线程安全机制:
- 分段锁:将整个Map分成多个段,每个段使用独立的锁
- 段内同步:每个段使用ReentrantLock进行同步
- 读不加锁:读取操作不需要加锁,提高并发性能
- 写加锁:写入操作只锁定对应的段,减少锁竞争
- 原子操作:提供原子操作方法如
putIfAbsent()、replace()等
Q: TreeMap和HashMap的性能对比如何?
A: 性能对比:
- 时间复杂度:HashMap平均O(1),TreeMap O(log n)
- 空间复杂度:HashMap略高(数组+链表),TreeMap较低(红黑树)
- 插入性能:HashMap更快,TreeMap需要维护树结构
- 查找性能:HashMap更快,TreeMap需要遍历树
- 遍历性能:TreeMap有序遍历更快,HashMap无序
- 内存使用:TreeMap更节省内存,HashMap有数组开销
- 理解底层实现:掌握各种Map的内部结构和实现原理
- 性能分析:能够分析不同操作的时间复杂度和性能特点
- 场景选择:根据具体需求选择合适的Map实现类
- 线程安全:理解并发环境下的使用方案和注意事项
- 最佳实践:了解性能优化和常见陷阱的解决方案
通过本章的学习,你应该已经掌握了Java Map集合的核心概念、实现原理和最佳实践。Map是Java开发中最常用的集合类型之一,深入理解其特性和使用场景,对于编写高效、可靠的Java程序至关重要。
评论