Java集合体系 —— Map篇
在 Java 集合框架中,Map 接口是与 Collection 接口并列的核心接口,它定义了一组 “键值对(Key-Value)” 形式的集合操作规范。与 Set 的 “去重” 核心价值不同,Map 的核心价值在于 “通过键快速查找值”,并通过不同实现类适配 “高性能查找”“保证插入顺序”“支持排序” 等多样化需求。本文将从 Map 接口概述入手,深入解析 HashMap、LinkedHashMap、TreeMap 三大实现类的底层结构、核心源码、性能特点及适用场景,为实际开发提供选择依据。
一、Map 接口概述
Map 接口是 Java 集合框架中独立的接口,不继承自 Collection 接口,其设计初衷是维护一组 “键唯一、值可重复” 的键值对,所有实现类需遵守以下核心规范:
1. 核心特性
- 键唯一性:集合中不会存在两个通过 equals () 方法判断为 true 的键(具体去重逻辑由实现类决定,如哈希表或比较器)。
- 值可重复性:不同的键可以对应相同的值,无唯一性要求。
- 无索引:不支持通过 int 类型索引访问键值对,遍历需依赖键集(keySet ())、值集(values ())或键值对集(entrySet ())。
- 无序性(默认):除 LinkedHashMap(保证插入顺序)和 TreeMap(保证排序顺序)外,多数实现类(如 HashMap)不保证键值对的存储 / 遍历顺序与插入顺序一致。
2. 主要方法
方法 | 功能 | 核心特性 |
---|---|---|
V put(K key, V value) | 向集合添加键值对 | 键不存在则添加并返回 null,键存在则覆盖值并返回旧值(键唯一核心) |
V remove(Object key) | 根据键移除键值对 | 键存在则移除并返回对应值,否则返回 null |
boolean containsKey(Object key) | 判断是否包含指定键 | 依赖实现类的查找逻辑(如哈希表查找或红黑树查找) |
boolean containsValue(Object value) | 判断是否包含指定值 | 需遍历值集,性能通常低于 containsKey () |
V get(Object key) | 根据键获取对应值 | 键存在则返回对应值,否则返回 null |
Set<K> keySet() | 返回所有键的集合 | 返回的 Set 集合特性与实现类的键存储特性一致(如无序 / 有序) |
Collection<V> values() | 返回所有值的集合 | 返回的 Collection 集合支持遍历,值可重复 |
Set<Map.Entry<K,V>> entrySet() | 返回所有键值对的集合 | 便于同时遍历键和值,性能优于分别遍历 keySet () 和 get () |
int size() | 返回键值对个数 | - |
void clear() | 清空所有键值对 | - |
void putAll(Map<? extends K, ? extends V> m) | 添加指定 Map 的所有键值对 | 键存在则覆盖值,不存在则新增 |
Map 接口本身不提供额外排序或顺序保证方法,所有差异化能力(如排序、顺序保证)均由具体实现类扩展。
二、HashMap 详解与源码分析
HashMap 是 Map 接口最常用的实现类,其底层通过哈希表(数组 + 链表 + 红黑树,JDK 1.8+)实现高效的键值对存储与查找,核心优势是 “高性能增删改查”。
1. 底层数据结构
HashMap 的底层结构为 “哈希表”,由数组(称为 “桶”,bucket)、链表和红黑树组成:
- 数组:存储键值对节点(Node<K,V>),每个数组元素对应一个 “桶”,桶的索引通过键的哈希值计算得出。
- 链表:当多个键的哈希值计算出相同的桶索引(哈希冲突)时,通过链表存储这些键值对节点;若链表长度超过 8,且数组长度大于 64,则转为红黑树。
- 红黑树:优化哈希冲突严重时的查询性能,将链表的 O (n) 查询时间复杂度降至 O (log n)。
2. 核心源码分析
(1)构造方法:初始化哈希表
HashMap 的构造方法主要用于初始化数组容量(默认 16)和负载因子(默认 0.75),负载因子用于平衡性能与内存开销:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {// 默认初始容量(16,必须是2的幂)static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;// 默认负载因子static final float DEFAULT_LOAD_FACTOR = 0.75f;// 链表转红黑树的阈值(8)static final int TREEIFY_THRESHOLD = 8;// 存储键值对的数组(桶)transient Node<K,V>[] table;// 空参构造:使用默认容量和负载因子public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR;}// 指定初始容量:使用默认负载因子public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}// 指定初始容量和负载因子public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " + loadFactor);this.loadFactor = loadFactor;// 计算大于等于initialCapacity的最小2的幂(保证哈希计算均匀)this.threshold = tableSizeFor(initialCapacity);}
}
(2)添加键值对:put (K key, V value)—— 核心逻辑
HashMap 的 put 方法是实现 “键唯一” 和 “高效存储” 的核心,流程包括哈希计算、桶定位、冲突处理和扩容判断:
public V put(K key, V value) {// 调用putVal方法,传入键的哈希值、键、值及默认参数return putVal(hash(key), key, value, false, true);
}// 核心添加逻辑
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;// 1. 若数组未初始化或长度为0,先初始化数组(resize()方法)if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;// 2. 计算桶索引((n-1) & hash),若桶为空,直接创建新节点放入桶中if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);// 3. 桶不为空,处理哈希冲突else {Node<K,V> e; K k;// 3.1 若桶中第一个节点的键与当前键相同(哈希值+equals()均匹配),记录该节点if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))e = p;// 3.2 若桶中节点是红黑树节点,调用红黑树的插入方法else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);// 3.3 若桶中节点是链表节点,遍历链表else {for (int binCount = 0; ; ++binCount) {// 3.3.1 遍历到链表尾部,创建新节点加入链表if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);// 3.3.2 若链表长度超过阈值,将链表转为红黑树if (binCount >= TREEIFY_THRESHOLD - 1)treeifyBin(tab, hash);break;}// 3.3.3 遍历中找到键相同的节点,跳出循环if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}// 4. 若找到键相同的节点(e != null),根据onlyIfAbsent决定是否覆盖值if (e != null) {V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e); // LinkedHashMap重写此方法,用于维护插入顺序return oldValue;}}++modCount; // 修改计数器,用于快速失败机制// 5. 若键值对数量超过阈值(容量*负载因子),触发扩容if (++size > threshold)resize();afterNodeInsertion(evict); // LinkedHashMap重写此方法,用于删除最少使用元素(LRU)return null;
}
关键逻辑:
- 哈希计算:通过 hash (key) 方法对键的 hashCode () 进行二次哈希,减少哈希冲突。
- 桶定位:通过 (n-1) & hash 计算桶索引,n 为数组长度(2 的幂),保证索引在数组范围内。
- 键唯一判断:先比较哈希值,再通过 equals () 方法验证,两者均匹配则视为相同键。
- 扩容机制:当键值对数量超过 “容量 * 负载因子” 时,数组容量翻倍(仍为 2 的幂),重新分配所有键值对。
(3)获取值:get (Object key)
get 方法通过键的哈希值定位桶,再遍历链表或红黑树查找对应键值对:
public V get(Object key) {Node<K,V> e;// 调用getNode方法,传入键的哈希值和键return (e = getNode(hash(key), key)) == null ? null : e.value;
}final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;// 1. 数组初始化且桶不为空if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {// 2. 检查桶中第一个节点if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))return first;// 3. 桶中节点不止一个,遍历查找if ((e = first.next) != null) {// 3.1 红黑树节点,调用红黑树查找方法if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);// 3.2 链表节点,遍历链表do {if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;
}
3. 性能特点
- 增删改查:无哈希冲突时,时间复杂度为 O (1);哈希冲突严重时,链表转为红黑树,时间复杂度为 O (log n)。
- 扩容开销:扩容时需重新计算所有键的桶索引并迁移,元素数量多时开销较大,建议初始化时指定合适容量。
- 内存占用:哈希表存在负载因子,预留部分空桶,存在少量空间浪费;键为 null 时仅占用一个桶(哈希值视为 0)。
- 线程安全:非线程安全,多线程并发修改会抛出 ConcurrentModificationException,需通过 Collections.synchronizedMap (new HashMap<>()) 或 ConcurrentHashMap 实现同步。
4. 适用场景
- 无需保证键值对顺序,仅需通过键快速查找值的场景(如存储用户信息,键为用户 ID,值为用户对象);
- 频繁执行增删改查操作,对性能要求高的场景;
- 允许键为 null(仅 1 个)、值为 null 的场景。
三、LinkedHashMap 详解与源码分析
LinkedHashMap 是 HashMap 的子类,其底层在 HashMap 的哈希表基础上,额外维护了一个双向链表,用于记录键值对的插入顺序或访问顺序,核心优势是 “高性能 + 顺序保证”。
1. 底层数据结构
LinkedHashMap 的底层结构为 “哈希表 + 双向链表”:
- 哈希表:继承自 HashMap,保证增删改查的高效性(同 HashMap)。
- 双向链表:每个键值对节点(Entry<K,V>)额外存储 prev(前驱)和 next(后继)引用,按插入顺序或访问顺序串联所有节点,确保遍历顺序可控。
- 节点类型:LinkedHashMap 的 Entry 节点继承自 HashMap 的 Node 节点,新增 prev 和 next 字段:
static class Entry<K,V> extends HashMap.Node<K,V> {Entry<K,V> before, after;Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next);}
}
- 顺序模式:通过 accessOrder 字段控制顺序(默认 false,按插入顺序;true 时按访问顺序,即最近访问的节点移至链表尾部,支持 LRU 缓存)。
2. 核心源码分析
(1)构造方法:初始化与顺序设置
LinkedHashMap 的构造方法继承自 HashMap,额外通过 accessOrder 字段设置顺序模式:
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {// 双向链表的头节点(最早插入/访问的节点)transient Entry<K,V> head;// 双向链表的尾节点(最晚插入/访问的节点)transient Entry<K,V> tail;// 顺序模式:false=插入顺序,true=访问顺序final boolean accessOrder;// 空参构造:默认插入顺序,使用默认容量和负载因子public LinkedHashMap() {super();accessOrder = false;}// 指定初始容量:默认插入顺序public LinkedHashMap(int initialCapacity) {super(initialCapacity);accessOrder = false;}// 指定初始容量和负载因子:默认插入顺序public LinkedHashMap(int initialCapacity, float loadFactor) {super(initialCapacity, loadFactor);accessOrder = false;}// 指定顺序模式:可设置插入顺序或访问顺序public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;}
}
(2)顺序维护:重写 HashMap 的钩子方法
LinkedHashMap 通过重写 HashMap 的 afterNodeAccess、afterNodeInsertion、afterNodeRemoval 三个钩子方法,维护双向链表的顺序:
- afterNodeAccess:访问节点后调用(如 get、put 覆盖值),accessOrder 为 true 时,将当前节点移至链表尾部(更新访问顺序):
void afterNodeAccess(Node<K,V> e) {Entry<K,V> evictable;if (accessOrder && (evictable = tail) != e) {Entry<K,V> p = (Entry<K,V>)e, b = p.before, a = p.after;p.after = null;// 移除当前节点if (b == null)head = a;elseb.after = a;if (a != null)a.before = b;elseevictable = b;// 将当前节点移至尾部if (evictable == null)head = p;else {p.before = evictable;evictable.after = p;}tail = p;++modCount;}
}
- afterNodeInsertion:插入节点后调用,可用于删除最少使用的节点(LRU 缓存逻辑),通过 removeEldestEntry 方法判断是否删除头节点(最早访问的节点):
void afterNodeInsertion(boolean evict) {Entry<K,V> first;// evict为true且需要删除最老节点时,移除头节点if (evict && (first = head) != null && removeEldestEntry(first)) {K key = first.key;removeNode(hash(key), key, null, false, true);}
}// 可重写此方法,自定义删除最老节点的条件(如容量超过阈值)
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false; // 默认不删除
}
- afterNodeRemoval:删除节点后调用,从双向链表中移除当前节点,保证链表完整性:
void afterNodeRemoval(Node<K,V> e) {Entry<K,V> p = (Entry<K,V>)e, b = p.before, a = p.after;p.before = p.after = null;// 移除当前节点if (b == null)head = a;elseb.after = a;if (a == null)tail = b;elsea.before = b;
}
(3)遍历性能:按链表顺序遍历
LinkedHashMap 的 entrySet () 遍历依赖双向链表,直接从 head 遍历到 tail,无需遍历哈希表的空桶,遍历性能优于 HashMap:
public Set<Map.Entry<K,V>> entrySet() {Set<Map.Entry<K,V>> es;// 返回LinkedEntrySet,遍历逻辑基于双向链表return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}private class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {public Iterator<Map.Entry<K,V>> iterator() {return new LinkedEntryIterator();}// 基于双向链表的迭代器private class LinkedEntryIterator extends LinkedHashIterator implements Iterator<Map.Entry<K,V>> {public Map.Entry<K,V> next() {return nextNode(); // 从head开始,依次获取next节点}}
}
3. 性能特点
- 增删改查:同 HashMap,无冲突时 O (1),但需额外维护双向链表,性能略低于 HashMap。
- 遍历性能:时间复杂度 O (n),且比 HashMap 更快(无空桶遍历开销),顺序严格按插入或访问顺序。
- 内存占用:每个节点需额外存储 prev 和 next 引用,内存开销高于 HashMap。
- 线程安全:同 HashMap,非线程安全,多线程场景需额外同步。
- LRU 缓存支持:设置 accessOrder=true 并重写 removeEldestEntry 方法,可实现 LRU(最近最少使用)缓存。
4. 适用场景
- 需要保证键值对插入顺序或访问顺序的场景(如记录用户操作日志,按操作顺序存储;实现 LRU 缓存);
- 频繁遍历键值对,对遍历性能要求高的场景;
- 允许键为 null(仅 1 个)、值为 null 的场景。
四、TreeMap 详解与源码分析
TreeMap 是唯一支持 “键排序” 的 Map 实现类,其底层通过红黑树(自平衡二叉搜索树)实现键值对的有序存储,核心优势是 “有序存储 + 键排序”。
1. 底层数据结构
TreeMap 的底层结构为 “红黑树”,一种自平衡的二叉搜索树,具有以下特性:
- 二叉搜索树特性:任意节点的左子树所有键小于该节点的键,右子树所有键大于该节点的键,中序遍历可得到有序键集。
- 红黑树平衡特性:通过颜色规则(节点为红色或黑色)保证树的高度平衡,避免退化为链表,确保增删查操作时间复杂度为 O (log n)。
- 排序方式:支持两种排序方式:
- 自然排序:键实现 Comparable 接口,通过 compareTo () 方法比较大小(默认)。
- 定制排序:构造 TreeMap 时传入 Comparator,通过 compare () 方法定义比较规则。
- 节点类型:TreeMap 的 Entry 节点存储键、值、左子树、右子树、父节点及颜色信息:
static final class Entry<K,V> implements Map.Entry<K,V> {K key;V value;Entry<K,V> left;Entry<K,V> right;Entry<K,V> parent;boolean color = BLACK; // 默认黑色节点
}
2. 核心源码分析
(1)构造方法:初始化排序规则
TreeMap 的构造方法核心是指定排序规则(自然排序或定制排序),内部通过 Comparator 字段存储比较器:
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, Serializable {// 比较器,null表示自然排序(键需实现Comparable)private final Comparator<? super K> comparator;// 红黑树的根节点private transient Entry<K,V> root;// 空参构造:默认自然排序,键需实现Comparable接口public TreeMap() {comparator = null;}// 传入Comparator:定制排序public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator;}// 传入Map:按自然排序初始化public TreeMap(Map<? extends K, ? extends V> m) {comparator = null;putAll(m);}// 传入SortedMap:使用其比较器,保持排序规则一致public TreeMap(SortedMap<K, ? extends V> m) {comparator = m.comparator();try {buildFromSorted(m.size(), m.entrySet().iterator(), null, null);} catch (java.io.IOException cannotHappen) {} catch (ClassNotFoundException cannotHappen) {}}
}
(2)添加键值对:put (K key, V value)—— 排序与键唯一核心
TreeMap 的 put 方法通过红黑树的插入逻辑实现键的排序和唯一性判断,依赖比较器或 Comparable 接口:
public V put(K key, V value) {Entry<K,V> t = root;// 1. 红黑树为空,创建根节点if (t == null) {compare(key, key); // 验证键是否可比较(自然排序时键需实现Comparable)root = new Entry<>(key, value, null);size = 1;modCount++;return null;}int cmp;Entry<K,V> parent;// 2. 获取比较器(自然排序时为null)Comparator<? super K> cpr = comparator;if (cpr != null) {// 3. 定制排序:遍历红黑树,找到插入位置do {parent = t;cmp = cpr.compare(key, t.key);if (cmp < 0) // 当前键小于节点键,向左子树查找t = t.left;else if (cmp > 0) // 当前键大于节点键,向右子树查找t = t.right;else // 键相等,覆盖值并返回旧值(键唯一核心)return t.setValue(value);} while (t != null);} else {// 4. 自然排序:键需实现Comparable,否则抛出ClassCastExceptionif (key == null)throw new NullPointerException();@SuppressWarnings("unchecked")Comparable<? super K> k = (Comparable<? super K>) key;do {parent = t;cmp = k.compareTo(t.key);if (cmp < 0)t = t.left;else if (cmp > 0)t = t.right;elsereturn t.setValue(value);} while (t != null);}// 5. 创建新节点,插入红黑树Entry<K,V> e = new Entry<>(key, value, parent);if (cmp < 0)parent.left = e;elseparent.right = e;// 6. 插入后修复红黑树平衡(调整颜色和旋转)fixAfterInsertion(e);size++;modCount++;return null;
}
关键逻辑:
- 键可比较验证:自然排序时,键必须实现 Comparable 接口,否则抛出 ClassCastException;不允许键为 null(无法比较)。
- 键唯一判断:通过比较器的 compare () 或 Comparable 的 compareTo () 方法判断键是否相等,返回 0 则视为相同键,覆盖值。
- 红黑树平衡修复:插入新节点后,通过 fixAfterInsertion 方法调整节点颜色和树的旋转(左旋转、右旋转),保证红黑树平衡。
(3)排序相关方法:NavigableMap 接口扩展
TreeMap 实现了 NavigableMap 接口,提供丰富的排序相关方法,支持键的范围查询和极值获取:
// 返回最小的键
public K firstKey() {return key(getFirstEntry());
}// 返回最大的键
public K lastKey() {return key(getLastEntry());
}// 返回小于key的最大键
public K lowerKey(K key) {return key(getLowerEntry(key));
}// 返回大于等于key的最小键
public K ceilingKey(K key) {return key(getCeilingEntry(key));
}// 返回从fromKey(含)到toKey(不含)的子Map
public SortedMap<K,V> subMap(K fromKey, K toKey) {return subMap(fromKey, true, toKey, false);
}// 返回小于toKey的所有键值对组成的子Map
public SortedMap<K,V> headMap(K toKey) {return headMap(toKey, false);
}// 返回大于等于fromKey的所有键值对组成的子Map
public SortedMap<K,V> tailMap(K fromKey) {return tailMap(fromKey, true);
}
3. 性能特点
- 增删改查:时间复杂度均为 O (log n),红黑树的平衡操作保证了稳定的性能。
- 遍历性能:中序遍历时间复杂度 O (n),直接得到有序键集,无需额外排序。
- 随机访问:无索引,通过键获取值需遍历红黑树,性能低于 HashMap。
- 内存占用:红黑树节点需存储左子树、右子树、父节点引用及颜色标记,内存开销高于 HashMap 和 LinkedHashMap。
- 线程安全:非线程安全,多线程场景推荐使用 ConcurrentSkipListMap(线程安全的有序 Map,基于跳表实现,性能优于同步包装的 TreeMap)。
4. 适用场景
- 需要对键进行有序存储(升序 / 降序)的场景(如按日期排序的订单数据,键为日期,值为订单列表);
- 需要使用排序相关方法(如范围查询、获取最大 / 最小键)的场景;
- 不允许键为 null,且键可比较(实现 Comparable 或提供 Comparator)的场景。
五、三种 Map 实现类的对比与适用场景
对比方式 | HashMap | LinkedHashMap | TreeMap |
---|---|---|---|
底层结构 | 哈希表(数组 + 链表 + 红黑树) | 哈希表 + 双向链表 | 红黑树(自平衡二叉搜索树) |
键值对顺序 | 无序(随机) | 有序(插入顺序 / 访问顺序) | 有序(自然排序 / 定制排序) |
键唯一依据 | hashCode() + equals() | hashCode() + equals() | compareTo()/compare() |
是否允许键为 null | 是(仅 1 个) | 是(仅 1 个) | 否(会抛 NPE) |
是否允许值为 null | 是 | 是 | 是 |
时间复杂度(增删改查) | O (1)(无冲突);O (log n)(冲突) | O (1)(无冲突,略慢);O (log n)(冲突) | O(log n) |
遍历性能 | 较慢(需遍历空桶) | 较快(按链表顺序) | 中等(红黑树中序遍历,有序) |
内存占用 | 较低 | 中等(额外存储链表引用) | 较高(存储树结构和颜色) |
线程安全 | 否 | 否 | 否 |
核心优势 | 高性能增删改查 | 高性能 + 顺序保证(插入 / 访问) | 有序存储 + 键排序 |
适用场景选择建议
-
HashMap:
- 优先选择场景:无需保证键值对顺序,仅需通过键快速查找值,且频繁执行增删改查操作(如缓存用户信息、存储配置参数);
- 避坑点:存储自定义对象作为键时,必须重写 hashCode () 和 equals ();多线程场景需注意同步。
-
LinkedHashMap:
- 优先选择场景:需要保证键值对插入顺序或访问顺序(如实现 LRU 缓存、记录操作日志),且频繁遍历;
- 避坑点:内存开销高于 HashMap,不适合元素数量极大的场景;多线程场景需额外同步。
-
TreeMap:
- 优先选择场景:需要对键进行有序存储,或需要使用范围查询、极值获取等排序相关方法(如按时间排序的日志数据、有序字典);
- 避坑点:键需实现 Comparable 或提供 Comparator;不支持键为 null;多线程场景推荐使用 ConcurrentSkipListMap。
六、总结
Map 接口及三大实现类是 Java 集合框架中 “键值对存储与查找” 需求的核心解决方案,其设计各有侧重:
- HashMap:以 “高性能” 为核心,牺牲顺序,适合纯键值对查找场景,是开发中的默认首选;
- LinkedHashMap:在 HashMap 基础上增加 “顺序保证”,平衡性能与顺序需求,支持 LRU 缓存等特殊场景;
- TreeMap:以 “有序存储” 为核心,牺牲部分性能,适合需要键排序或范围查询的场景。
在实际开发中,还要注意线程安全的问题,但上述的三种实现类并不能保证时线程安全的,当然,在java集合体系中是有确保线程安全的集合,这里不再叙述,后续的文章可能会涉及。