所有哈希映射键均小写

2022-09-02 09:16:01

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

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

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

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


答案 1

您可以尝试使用 不区分大小写的排序,而不是使用 。这将避免创建每个密钥的小写版本:HashMapTreeMap

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

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

以下是一些类似的问题:


答案 2

在循环访问地图时无法移除该条目。如果您尝试这样做,您将有一个 ConcurentModificationException。

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

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

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

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

对于拆分算法,您可以使用流 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());
    }
}