HashMap 初始化应预估容量避免扩容开销,线程安全场景慎选ConcurrentHashMap,键需重写equals/hashCode,遍历用entrySet()而非keySet(),顺序需求换LinkedHashMap,有序查询用TreeMap。

HashMap 声明和初始化要选对构造函数
直接用 new HashMap() 看似简单,但默认初始容量是 16,负载因子 0.75。如果预估要存 100 个键值对,不指定初始容量会导致至少 3 次扩容(16 → 32 → 64 → 128),每次扩容都要 rehash 所有已有元素,性能白丢。
- 已知大概数量时,用
new HashMap(expectedSize / 0.75 + 1)向上取整算初始容量,比如存 100 个,建议写new HashMap(134) - 需要线程安全?别直接改用
ConcurrentHashMap—— 它不是HashMap的线程安全版“替代品”,而是不同设计目标的实现;若只是偶尔读多写少,加Collections.synchronizedMap()更轻量 - 键类型必须正确重写
equals()和hashCode(),否则get()找不到、put()可能重复插入——String、Integer 等 JDK 类已实现,自定义类务必检查
put() 和 get() 的行为边界必须清楚
put() 总是返回旧值(或 null),不是操作是否成功的布尔值;get() 找不到时也返回 null,这和「值本身为 null」无法区分,容易引发 NPE 或逻辑错判。
- 判断键是否存在,用
containsKey(key),别依赖get(key) != null - 想避免
null值带来的歧义,可用computeIfAbsent(key, k -> defaultValue)替代先get()再put()的惯用写法 -
put(null, "value")是允许的(键可为null),但仅限一个;get(null)能取到,不过一旦用了ConcurrentHashMap,null键会直接抛NullPointerException
遍历方式选错会导致 ConcurrentModificationException 或性能问题
在循环中调用 remove() 或 put() 是常见崩溃源头;另外,用 keySet().iterator() 遍历再查 get() 是 O(n²) 行为,尤其当 value 是复杂对象时更隐蔽。
- 安全删除:用
Iterator.remove(),不要在 for-each 中直接调map.remove() - 高效遍历键值对:用
entrySet(),例如for (Map.Entry
,避免反复 hash 查找e : map.entrySet()) { ... } - Java 8+ 推荐用
forEach((k, v) -> {...})或流式处理,但注意stream().filter(...).collect(...)会新建 Map,原 Map 不变
HashMap 不是万能的,该换容器时别硬扛
如果需要按插入顺序迭代,HashMap 不保证顺序——得换 LinkedHashMap;如果 key 是有序比较场景(如范围查询),TreeMap 更合适;如果 key 是枚举,EnumMap 内存和速度都碾压 HashMap。
立即学习“Java免费学习笔记(深入)”;
-
LinkedHashMap构造时传true可启用访问顺序(LRU 缓存基础),但注意它不是线程安全的 -
TreeMap的subMap()、headMap()支持区间操作,HashMap完全不支持 - 频繁只读访问 + 固定数据?考虑用
Map.of()(Java 9+)或ImmutableMap(Guava),避免意外修改和同步开销
真正麻烦的从来不是怎么放进去,而是放进去之后你忘了它不排序、不线程安全、容忍 null 键但不兼容并发容器、扩容代价藏在看似无害的 put() 里。










