hashmap是java键值查找首选,平均o(1)时间复杂度;需正确重写hashcode()和equals(),禁用可变对象作键;treemap适用于排序与范围查询,但查找为o(log n);并发场景应使用concurrenthashmap。

用 HashMap 做键值查找最直接
Java 中基于键的查找,核心是用哈希表结构——HashMap 是首选。它平均时间复杂度 O(1),不保证顺序,但查找快、写法简洁。
常见错误是把 ArrayList 或 LinkedList 当成键值容器来用,结果写一堆循环遍历 get(i) 再比对字段,既慢又易错。
-
HashMap的键必须正确重写hashCode()和equals(),否则get(key)返回null(哪怕键“看起来一样”) - 如果键是自定义对象,别只重写
equals()忘了hashCode();两者必须逻辑一致 - 避免用可变字段(如
StringBuilder、未冻结的ArrayList)作键,否则哈希值变化后无法再查到
示例:
Map<String, User> userMap = new HashMap<>();
userMap.put("u1001", new User("Alice"));
User u = userMap.get("u1001"); // 直接拿到,不用遍历
需要有序遍历时选 TreeMap,但别为排序牺牲查找性能
TreeMap 按键自然序或自定义比较器排序,get() 时间复杂度是 O(log n)。它适合既要按键查、又要范围查询(如 subMap("a", "m"))或迭代时保持顺序的场景。
容易踩的坑是:误以为 TreeMap 比 HashMap “更规范”或“更安全”,就默认选用。其实只要不需要排序,它纯属性能降级。
立即学习“Java免费学习笔记(深入)”;
- 键类必须实现
Comparable,或构造时传入Comparator,否则运行时报ClassCastException -
TreeMap不允许null键(HashMap允许一个null键),插入会抛NullPointerException - 如果只是偶尔按范围查,但 95% 操作是单键
get(),优先用HashMap+ 额外维护一个TreeSet存键(视业务权衡)
并发环境下不能直接用 HashMap 或 TreeMap
多线程往非线程安全集合里写 + 查,会出现 ConcurrentModificationException、数据丢失、甚至死循环(HashMap 扩容时链表成环)。别指望加个 synchronized 块就万事大吉——锁粒度太粗,吞吐低。
- 高并发读多写少:用
ConcurrentHashMap,它支持并发读、分段/细粒度写锁,get()无锁,性能接近HashMap - 需要强一致性且写操作频繁:考虑
CopyOnWriteArrayList不适用(它是列表),得换方案,比如读写锁 +HashMap,或用外部同步机制 -
ConcurrentHashMap的computeIfAbsent()是原子的,适合缓存加载场景;别用containsKey() + put()组合,可能重复计算
别忽略 Map 接口方法的语义差异
同一个“查找”动作,不同方法行为差别很大,用错会导致空指针、逻辑错或性能问题。
-
get(key):键不存在返回null—— 如果值本身允许为null,就无法区分“没找到”和“值就是null” -
getOrDefault(key, defaultValue):安全替代get()+ 判空,推荐在有默认值语义时直接用 -
computeIfAbsent(key, mappingFunction):键不存在才执行函数生成值并放入,整个过程原子,适合懒加载缓存 -
containsKey(key)是查键,containsValue(value)是遍历查值——后者是 O(n),慎用
真正容易被忽略的是:当你的键类型是 Integer、String 这类不可变类时,get() 看似简单,但一旦换成自定义类,equals() 和 hashCode() 的实现质量,直接决定查找是否成立——这里没捷径,必须测。










