小写所有 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
中可能有key
和Key
。那么你的地图应该如何表现呢?
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 键的主要内容,如果未能解决你的问题,请参考以下文章