频次统计首选hashmap,初始化用new hashmap(),更新用merge;多条件分组宜用自定义不可变key;并发选concurrenthashmap,内存敏感需定期清理或弱引用。

统计字符串中每个字符出现次数
最常见需求是频次统计,HashMap 是首选:它允许 null 值(但不推荐键为 null),插入和查询平均时间复杂度为 O(1)。注意别用 TreeMap,除非你明确需要按键排序——它会把统计结果按字符 Unicode 排序,但代价是 O(log n) 每次操作。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 初始化用
new HashMap<character integer>()</character>,避免原始类型自动装箱开销过大时改用IntObjectMap<integer></integer>(来自 Trove 或 fastutil 库) - 更新计数推荐用
map.merge(ch, 1, Integer::sum),比先get再判空再put更简洁且线程安全(单线程下) - 若输入含大量重复短字符串(如日志行),可提前用
String.intern()减少 key 对象创建,但要注意常量池压力
多条件分组统计用嵌套 Map 还是自定义 Key
比如统计 “城市 + 年龄段” 的用户数,有人写 Map<string map long>></string>,这容易引发空指针、遍历繁琐、序列化困难。更稳妥的是组合 key:
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 定义静态内部类
CityAgeKey,重写equals()和hashCode(),字段用final保证不可变 - 避免用
Arrays.asList(city, age)当 key——List的hashCode依赖元素顺序且开销大 - 如果 key 字段较多或动态变化,考虑用
Map.of(city, age)(Java 16+)生成不可变 map,但注意它不实现hashCode一致契约,不能直接当 key
并发场景下用 ConcurrentHashMap 还是 Collections.synchronizedMap
当多个线程同时更新统计结果(如实时日志聚合),ConcurrentHashMap 是默认选择;Collections.synchronizedMap 本质是全表锁,吞吐量差很多。
实操建议:
立即学习“Java免费学习笔记(深入)”;
-
ConcurrentHashMap的computeIfAbsent和merge是原子操作,适合“查无则建、有则加”的统计模式 - 慎用
size()——它只是估算值;需精确总数时改用mappingCount()(返回long) - 如果统计逻辑复杂(比如要先查再算再存),别在 lambda 里塞太多逻辑,容易阻塞分段锁;拆成先计算后
put更可控
内存敏感场景下如何避免 Map 膨胀
长时间运行的统计服务(如监控指标聚合),HashMap 容量只增不减,即使大部分 key 已失效,也会持续占内存。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 定期清理:用
keySet().removeIf(k -> shouldExpire(k)),但注意ConcurrentHashMap不支持该操作,得用newKeySet配合removeAll - 用
WeakHashMap存储临时中间结果(key 是弱引用),但仅适用于 key 本身由外部强引用控制的场景;统计主表千万别用 - 真正压测发现 GC 压力大时,考虑用
ChronoUnit.MINUTES.between(lastAccess, now) > 5替代绝对时间戳做 LRU 清理,减少Date对象分配
Map 统计看着简单,但数据生命周期、并发边界、key 设计这三个地方一动就容易出隐蔽问题,尤其是线上跑了两周才 OOM 的那种。









