跳到主要内容

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接口的设计遵循以下几个核心原则:

键值对原则

提供键与值之间的映射关系,支持基于键的快速访问

键唯一性原则

确保键的唯一性,避免数据冲突和覆盖

高效查找原则

基于哈希算法实现高效的查找和访问

灵活操作原则

支持丰富的操作方法,满足各种使用场景

Map接口核心方法示例
java
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(); // 清空Map
18
19 // ========== 查询操作 ==========
20 int size(); // 获取元素个数
21 boolean isEmpty(); // 判断是否为空
22}

1.4 Map接口核心方法详解

Map接口提供了丰富的方法来操作键值对集合,这些方法可以分为几个主要类别:

Map基本操作方法
java
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)检查键是否存在booleanHashMap为O(1),TreeMap为O(log n)
containsValue(Object)检查值是否存在boolean遍历全部元素,性能较差O(n)

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 实现类详解

2.1 HashMap 概述

核心特点

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

  • 🔍 哈希表实现:基于数组+链表+红黑树的数据结构
  • 🔀 无序性:不保证元素的顺序
  • 允许null:允许null键和null值
  • ⚠️ 线程不安全:在多线程环境下需要外部同步
  • 高效操作:查找、插入、删除性能好,平均O(1)

适用场景

  • 一般的键值对存储需求
  • 需要高效的查找、插入、删除操作
  • 对元素顺序没有要求
  • 单线程环境或已进行外部同步

2.2 HashMap 内部结构

HashMap基于哈希表实现,内部使用数组+链表+红黑树的数据结构。当链表长度超过阈值时,会转换为红黑树以提高性能。

核心字段

HashMap核心字段
java
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; // 16
6
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}

构造方法

HashMap构造方法
java
1public class HashMap<K,V> extends AbstractMap<K,V> {
2
3 /**
4 * 构造一个默认初始容量(16)和默认负载因子(0.75)的空HashMap
5 */
6 public HashMap() {
7 this.loadFactor = DEFAULT_LOAD_FACTOR;
8 }
9
10 /**
11 * 构造一个指定初始容量的空HashMap
12 */
13 public HashMap(int initialCapacity) {
14 this(initialCapacity, DEFAULT_LOAD_FACTOR);
15 }
16
17 /**
18 * 构造一个指定初始容量和负载因子的空HashMap
19 */
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元素的HashMap
34 */
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使用高效的哈希算法来计算键的哈希值:

HashMap哈希算法
java
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支持两种节点类型:链表节点和红黑树节点:

HashMap节点结构
java
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 添加元素

HashMap添加元素实现
java
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的元素个数超过阈值时,会自动扩容:

HashMap扩容机制
java
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 else
59 loTail.next = e;
60 loTail = e;
61 }
62 else {
63 if (hiTail == null)
64 hiHead = e;
65 else
66 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 性能优化建议

HashMap性能优化示例
java
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 基本操作示例

HashMap基本操作示例
java
1public class HashMapBasicExample {
2
3 public static void main(String[] args) {
4 // 创建HashMap
5 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 高级操作示例

HashMap高级操作示例
java
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}
28
29</TabItem>
30<TabItem value="linkedhashmap" label="LinkedHashMap 实现">
31
32### 3.1 LinkedHashMap 概述
33
34:::tip[核心特点]
35LinkedHashMapHashMap的子类,它在HashMap的基础上维护了一个双向链表,具有以下特点:
36- 🔄 **继承HashMap**:拥有HashMap的所有特性
37- 📝 **维护顺序**:维护插入顺序或访问顺序
38- 🔗 **双向链表**:通过双向链表维护元素顺序
39- 🗄️ **LRU缓存**:可以实现LRU(最近最少使用)缓存
40- 📉 **性能略低**:相比HashMap有轻微的性能开销
41:::
42
43#### 适用场景
44- 需要保持元素插入顺序的场景
45- 需要实现LRU缓存的场景
46- 需要按访问顺序排序的场景
47- 对顺序有要求但不需要排序的场景
48
49### 3.2 LinkedHashMap 内部结构
50
51LinkedHashMapHashMap的基础上增加了双向链表来维护元素顺序:
52
53```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 节点链接

LinkedHashMap节点链接
java
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 访问顺序维护

LinkedHashMap访问顺序维护
java
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 else
16 b.after = a;
17 if (a != null)
18 a.before = b;
19 else
20 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 基本使用示例

LinkedHashMap基本使用示例
java
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缓存实现

LinkedHashMap实现LRU缓存
java
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 @Override
10 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.2 TreeMap 内部结构

TreeMap基于红黑树实现,每个节点包含键值对和左右子节点:

TreeMap核心字段
java
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 查找操作

TreeMap查找操作
java
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 else
30 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 else
51 return p;
52 }
53 }
54 return null;
55 }
56}

4.3.2 插入操作

TreeMap插入操作
java
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 else
28 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 else
43 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 else
50 parent.right = e;
51 fixAfterInsertion(e);
52 size++;
53 modCount++;
54 return null;
55 }
56}

4.4 TreeMap 使用示例

4.4.1 基本使用示例

TreeMap基本使用示例
java
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 导航方法示例

TreeMap导航方法示例
java
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分成多个段,每个段使用独立的锁:

ConcurrentHashMap核心字段
java
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 基本使用示例

ConcurrentHashMap基本使用示例
java
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 原子操作示例

ConcurrentHashMap原子操作示例
java
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则替换为2
9 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}
19
20## 6. 其他Map实现
21
22### 6.1 Hashtable
23
24HashtableJava早期提供的线程安全的Map实现,现在已经不推荐使用:
25
26```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); // 抛出NullPointerException
38 // map.put("key", null); // 抛出NullPointerException
39
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使用弱引用作为键,当键不再被强引用时,对应的键值对会被自动回收:

WeakHashMap示例
java
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()); // 2
13
14 // 释放强引用
15 key1 = null;
16 key2 = null;
17
18 // 触发GC
19 System.gc();
20
21 System.out.println("GC后: " + map.size()); // 0
22 }
23}

6.3 IdentityHashMap

IdentityHashMap使用==而不是equals()来比较键,适用于需要精确对象引用的场景:

IdentityHashMap示例
java
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()); // 1
14
15 // IdentityHashMap使用==比较
16 identityMap.put(key1, 1);
17 identityMap.put(key2, 2);
18 System.out.println("IdentityHashMap大小: " + identityMap.size()); // 2
19 }
20}

7. 实际应用场景

7.1 缓存系统

缓存系统示例
java
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秒TTL
43
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")); // null
57 }
58}

7.2 配置管理

配置管理示例
java
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 @FunctionalInterface
39 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 数据统计

数据统计示例
java
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::new
48 ));
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 性能优化策略

Map性能优化示例
java
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 常见陷阱和解决方案

注意事项
  1. HashMap的线程安全问题:多线程环境下需要外部同步
  2. TreeMap的null键限制:键不能为null,需要特殊处理
  3. ConcurrentHashMap的null限制:键和值都不能为null
  4. WeakHashMap的键回收:键可能被垃圾回收,需要谨慎使用
常见陷阱示例
java
1public class MapCommonTraps {
2
3 /**
4 * 线程安全问题
5 */
6 public static void threadSafetyTrap() {
7 Map<String, Integer> map = new HashMap<>();
8
9 // 错误:多线程环境下直接使用HashMap
10 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 // 正确:使用ConcurrentHashMap
23 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); // 抛出NullPointerException
34 } 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); // 抛出NullPointerException
42 } 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"))); // null
60 System.out.println("修改后的键获取值: " + badMap.get(key)); // 1
61 }
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 &amp; (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有数组开销
面试要点
  1. 理解底层实现:掌握各种Map的内部结构和实现原理
  2. 性能分析:能够分析不同操作的时间复杂度和性能特点
  3. 场景选择:根据具体需求选择合适的Map实现类
  4. 线程安全:理解并发环境下的使用方案和注意事项
  5. 最佳实践:了解性能优化和常见陷阱的解决方案

通过本章的学习,你应该已经掌握了Java Map集合的核心概念、实现原理和最佳实践。Map是Java开发中最常用的集合类型之一,深入理解其特性和使用场景,对于编写高效、可靠的Java程序至关重要。

评论