Строчные все ключи HashMap

Я столкнулся со сценарием, в котором я хочу прописать все ключи 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 так что у меня нет возможности

удаление каждого ключа после нижнего регистра - добавлено на новую карту

4 ответа

Решение

Вместо того, чтобы использовать HashMap, вы можете попробовать использовать TreeMap с учетом регистра без учета регистра. Это позволит избежать необходимости создавать строчную версию каждого ключа:

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

Как только вы создали эту карту, put() а также get() будет вести себя без учета регистра, поэтому вы можете сохранять и извлекать значения, используя строчные клавиши. Перебор ключей вернет их в исходном, возможно, в верхнем регистре.

Вот несколько похожих вопросов:

Вы не можете удалить запись во время итерации по карте. Если вы попытаетесь это сделать, у вас будет исключение ConcurentModificationException.

Так как проблема заключается в OutOfMemoryError, а не в ошибке производительности, использование параллельного потока также не поможет.

Несмотря на то, что в последнее время некоторые задачи в Stream API будут выполнены, это все равно приведет к тому, что в какой-то момент в памяти появятся две карты, поэтому проблема все равно останется.

Чтобы обойти это, я видел только два пути:

  • Дайте больше памяти вашему процессу (увеличивая -Xmx в командной строке Java). Память дешева в эти дни;)
  • Разделите карту и работайте на куски: например, вы делите размер карты на десять и обрабатываете один кусочек за раз и удаляете обработанные записи перед обработкой нового кусочка. Таким образом, вместо того, чтобы иметь два раза карту в памяти, у вас будет только 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());
    }
}

Проблемы в приведенных выше ответах верны, и вам может потребоваться пересмотреть изменение структуры данных, которую вы используете.

для меня у меня была простая карта, мне нужно было изменить ее клавиши на нижний регистр

взгляните на мой фрагмент, это тривиальное решение и плохая производительность

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)));
}

Насчет объема памяти не уверен. Если вы используете Kotlin, вы можете попробовать следующее.

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

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

Другие вопросы по тегам