小写所有 HashMap 键

Posted

技术标签:

【中文标题】小写所有 HashMap 键【英文标题】:Lowercase all HashMap keys 【发布时间】:2017-05-04 15:34:26 【问题描述】:

我遇到了一个场景,我想将 HashMap 的所有键都小写(不要问为什么,我只需要这样做)。 HashMap 有几百万个条目。

起初,我以为我只是创建一个新地图,遍历要小写的地图条目,然后添加相应的值。这个任务应该每天只运行一次或类似的东西,所以我想我可以忍受这个。

Map<String, Long> lowerCaseMap = new HashMap<>(myMap.size());
for (Map.Entry<String, Long> entry : myMap.entrySet()) 
   lowerCaseMap.put(entry.getKey().toLowerCase(), entry.getValue());

然而,当我的服务器在我即将复制地图的这段时间超载时,这会导致一些 OutOfMemory 错误。

现在我的问题是,我怎样才能以最小的内存占用完成这项任务?

是否会在小写后删除每个键 - 添加到新的地图帮助中?

我可以利用 java8 流来加快速度吗? (例如这样的)

Map<String, Long> lowerCaseMap = myMap.entrySet().parallelStream().collect(Collectors.toMap(entry -> entry.getKey().toLowerCase(), Map.Entry::getValue));

更新 好像是Collections.unmodifiableMap 所以我没有选择

小写后删除每个键 - 添加到新地图

【问题讨论】:

你不能一开始就插入小写的键吗? 您尝试做的事情很危险,因为在原始的Map 中可能有keyKey。那么你的地图应该如何表现呢? a) 不,这不太可能从并行处理中受益,b) 并行处理不能解决您的内存问题,c) 因为您自己已经回答了 remove 选项,所以只有一个解决方案:增加更多的堆内存。 @sestus:不,这将不会从并行处理中受益。解释原因将超出此问题的范围。基本上,标准的toMap 收集器必须合并部分结果,合并成本与任何节省一样高。 String.toLowerCase() 操作太便宜了,无法补偿开销。 我真的,真的认为你应该告诉我们你想这样做的原因。这可能是“我的指甲太长所以我不得不剪掉我的手(不要问我为什么必须这样做)”的情况。也许有比将不可修改的映射键小写更好的解决方案。 【参考方案1】:

不确定内存占用。如果使用 Kotlin,可以尝试以下方法。

val lowerCaseMap = myMap.mapKeys  it.key.toLowerCase() 

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/map-keys.html

【讨论】:

【参考方案2】:

上述答案中的问题是正确的,您可能需要重新考虑更改您正在使用的数据结构。

对我来说,我有一个简单的地图,我需要将其键更改为小写

看看我的 sn-p,它是一个简单的解决方案,而且性能很差

private void convertAllFilterKeysToLowerCase() 
    HashSet keysToRemove = new HashSet();
    getFilters().keySet().forEach(o -> 
        if(!o.equals(((String) o).toLowerCase()))
            keysToRemove.add(o);
    );
    keysToRemove.forEach(o -> getFilters().put(((String) o).toLowerCase(), getFilters().remove(o)));

【讨论】:

【参考方案3】:

您可以尝试使用不区分大小写的TreeMap,而不是使用HashMap。这将避免创建每个键的小写版本的需要:

Map<String, Long> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
map.putAll(myMap);

构建此映射后,put()get() 将不区分大小写,因此您可以使用全小写键保存和获取值。遍历键将返回它们的原始形式,可能是大写形式。

这里有一些类似的问题:

Case insensitive string as HashMap key Is there a good way to have a Map<String, ?> get and put ignoring case?

【讨论】:

这确实是一种有趣的方法。它不会更改 API,只需要不同的 Map 实例化。我尝试利用它。 看来这完成了这项工作。改变是微不足道的——只是 Map 的实现被改变了。谢谢!【参考方案4】:

您无法在遍历地图时删除该条目。如果您尝试这样做,您将遇到 ConcurentModificationException。

由于问题是 OutOfMemoryError,而不是性能错误,因此使用并行流也无济于事。

尽管最近将在 Stream API 上完成一些任务,但这仍然会导致内存中有两个映射,因此您仍然会遇到问题。

为了解决这个问题,我只看到了两种方法:

为您的进程提供更多内存(通过在 Java 命令行上增加 -Xmx)。现在内存很便宜;) 拆分地图并以块的形式工作:例如,您将地图的大小除以 10,然后一次处理一个块,并在处理新块之前删除已处理的条目。这样一来,内存中的地图就不是两倍,而是地图的 1.1 倍。

对于拆分算法,您可以使用 Stream API 尝试这样的操作:

Map<String, String> toMap = new HashMap<>();            
int chunk = fromMap.size() / 10;
for(int i = 1; i<= 10; i++)
    //process the chunk
    List<Entry<String, String>> subEntries = fromMap.entrySet().stream().limit(chunk)
        .collect(Collectors.toList());  

    for(Entry<String, String> entry : subEntries)
        toMap.put(entry.getKey().toLowerCase(), entry.getValue());
        fromMap.remove(entry.getKey());
    

【讨论】:

我只想引用@xenteros。如果映射中有key 键和Key 键,则只有小写映射的整个逻辑将失败,并且该任务将不再有意义,因为最终状态永远无法实现(如果您不只是删除其中一个) 假设这段时间没有使用原始地图。 (更新的)问题表明源映射根本不可变(即Collections.unmodifiableMap 返回的映射),因此无论您尝试删除块还是单个都没有关系条目(如果您使用Iterator.remove(),则可以同时迭代和删除)。 不,它没有被使用。我想分裂是我唯一的选择。我已经为我的 JVM 提供了尽可能多的内存。避免在内存中加载完整地图可以解决此问题。 @davidxxx 答案是在提供此附加信息之前提交的(或者至少它们彼此接近,所以我猜 loicmathieu 那时还不知道)

以上是关于小写所有 HashMap 键的主要内容,如果未能解决你的问题,请参考以下文章

HashMap集合

HashMap原理浅析

JavaSE8基础 HashMap isEmpty clear 判断该映射空不空与删除所有键值对

在 Java 中解析 HashMap

HashMap源码分析

JAVA基础——集合——HashMap