如何在Java中实现不区分大小写的单词计数,但以首次出现的原始形式作为键

使用treemap配合string.case_insensitive_order比较器,可在保持键值唯一性(忽略大小写)的同时,以单词首次出现的原始大小写形式作为最终键,精准统计词频。

在处理字符串词频统计时,若需“不区分大小写匹配,但保留首次出现的原始大小写作为键”,直接使用HashMap或ConcurrentHashMap并手动遍历判断(如原代码所示)不仅逻辑复杂、易出错,且存在并发安全与性能隐患(例如重复put、竞态条件、O(n²)时间复杂度)。更优雅、高效且线程安全(如需)的方案是借助TreeMap的定制化排序能力。

TreeMap支持传入Comparator super K>构造,而String.CASE_INSENSITIVE_ORDER正是一个预定义的、忽略大小写的Comparator。关键在于:TreeMap在插入时根据该比较器判定“相等性”,但实际存储的键仍是原始字符串对象——这意味着"AA"、"Aa"、"aa"会被视为同一逻辑键,但首次插入的"AA"将保留在Map中作为键,后续相同语义的字符串会命中该键并更新其值。

推荐写法(简洁、安全、符合语义):

import java.util.TreeMap;

final TreeMap wordCount = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
final String[] words = {"AA", "Bb", "Aa", "aa", "BB"};

for (String word : words) {
    wordCount.merge(word, 1, Integer:

:sum); } System.out.println(wordCount); // 输出: {AA=3, Bb=2}

对于更复杂的示例:
{"AAa", "aaa", "BBB", "bbb", "BbB", "AaA", "AAc"}
执行后输出:{AAa=3, BBB=3, AAc=1}
✅ "AAa"首次出现 → 成为键;"AaA"和"aaa"与其忽略大小写相等 → 累加至AAa;
✅ "BBB"首次出现 → 成为键;"bbb"和"BbB"匹配 → 累加至BBB;
✅ "AAc"无其他变体 → 单独计为1。

⚠️ 注意事项:

  • TreeMap非线程安全;若需并发环境使用,请搭配Collections.synchronizedSortedMap()包装,或改用ConcurrentSkipListMap(它也接受Comparator,且线程安全):
    final ConcurrentSkipListMap safeMap 
        = new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER);
  • 不要误用HashMap+toLowerCase()预处理:这会丢失原始大小写形式,无法满足“以首次出现为准”的需求;
  • merge()方法是Java 8引入的原子操作,比手动get/put更简洁可靠,避免空指针与竞态。

总结:以TreeMap(或ConcurrentSkipListMap)配合String.CASE_INSENSITIVE_ORDER为核心,辅以merge()语义化更新,即可一行逻辑解决“大小写不敏感分组 + 原始形式定键”的经典词频统计问题。