map接口仅定义键值查找行为,具体实现由hashmap(哈希表+链表/红黑树)、treemap(红黑树,要求key可比较)和linkedhashmap(哈希表+双向链表,维护插入/访问顺序)承担,三者在顺序性、null支持、性能特征上差异显著。

Map 的键值对不是“存进去就完事了”
Map 接口本身不实现存储,它只定义行为:用一个 key 查找对应的 value。真正干活的是它的实现类,比如 HashMap、TreeMap、LinkedHashMap。它们底层数据结构完全不同,直接影响你能不能按插入顺序遍历、key 是否必须可比较、null 能不能当 key 等实际问题。
常见误解是把 Map 当成“二维数组”或“表格”,其实它更像一个带索引的柜子:你给一个标签(key),柜子告诉你里面放了什么(value);但这个“标签怎么贴”“柜子怎么分格”,完全取决于具体实现。
HashMap 用哈希表 + 链表/红黑树存键值对
HashMap 是最常用实现,核心逻辑是:对每个 key 调用 hashCode() 得到整数,再通过位运算映射到内部数组(桶)的某个下标;如果多个 key 映射到同一个下标(哈希冲突),就用链表或红黑树串起来。
-
key必须正确重写hashCode()和equals(),否则get()找不到原来存的值 - 默认初始容量是 16,负载因子 0.75 —— 意味着存满 12 个元素就会触发扩容(rehash),开销不小
- Java 8 后,当单个桶里链表长度 ≥ 8 且数组长度 ≥ 64,链表会转为红黑树,避免最坏 O(n) 查找
-
null可以作为key(只允许一个),因为hashCode()不会被调用,它被硬编码放在数组索引 0 的位置
TreeMap 强制要求 key 实现 Comparable 或传入 Comparator
TreeMap 底层是红黑树,所有 key 按自然序或自定义序排列。这意味着:key 类型要么实现 Comparable 接口(如 String、Integer),要么构造时显式传入 Comparator,否则运行时报 ClassCastException。
- 不支持
null作key(除非你写的Comparator显式处理null) -
get()、put()时间复杂度稳定在 O(log n),没有哈希表的扩容抖动,但常数更大 - 能直接拿到子范围视图,比如
subMap("b", "f"),headMap("c"),这是HashMap做不到的
LinkedHashMap 记住插入/访问顺序,但代价是额外指针
LinkedHashMap 在 HashMap 基础上加了双向链表,把所有 Entry 按插入或访问顺序串起来。这带来两个关键能力:按插入顺序遍历、LRU 缓存淘汰。
- 构造时传
true可启用访问顺序模式(new LinkedHashMap(16, 0.75f, true)),get()也会触发节点移到链表尾 - 每次增删改查都要维护链表指针,内存占用比
HashMap略高,但遍历性能更可预测 - 注意:
keySet()、values()、entrySet()返回的集合都保持该顺序,不是“靠自己排序”
键值对模型的复杂性不在“怎么放”,而在“怎么找”和“怎么排”。选错实现类,轻则逻辑出错(比如用没重写 hashCode() 的对象当 HashMap 的 key),重则性能雪崩(比如在 TreeMap 里反复做范围查询却没意识到 log n 的系数)。真正用好 Map,得盯着你的 key 类型、访问模式、是否需要顺序,而不是只看接口声明。











