Структура данных HashMap, но значения добавляются вместо замены

#java #hashmap

#java #hashmap

Вопрос:

Я ищу структуру данных, которая почти точно соответствует a HashMap<String,Integer> , но проблема с хэш-картами заключается в том, что большая часть данных, хранящихся в парах ключ-значение, теряется при вызове putAll() метода для двух хэш-карт из-за поведения замены putVal() in line 655 of the java/util/HashMap.java .

Это в основном то изменение, которое я хочу:

     if (e != null) { // existing mapping for key
         V oldValue = e.value;
         if (!onlyIfAbsent || oldValue == null)
--            e.value = value;
              e.value = value   oldValue;
         afterNodeAccess(e);
         return oldValue;
    }
 

Существует ли существующая структура данных, которую я упустил из виду, которая могла бы сделать такую вещь, или как мне создать класс, который в основном является HashMap с этим одним изменением?

Я уже пытался что-то закодировать, но не работает так, как я хочу… На самом деле не имеет значения, устанавливаю ли я метод put на @Override, делаю это так или удаляю его полностью — поведение замены, конечно, остается прежним, потому putAll() putVal() что я не могу использовать / изменять извне — или я, по крайней мере, не знаю как…

  /**
  * doesn't work, putAll() uses putVal() that I can't reach
  */
 public class SumHashMap<K> extends HashMap<K, Integer> {
    private static final long serialVersionUID = 1L;

    public Integer put(K key, Integer value) {
        Integer oldValue = get(key);
        if (oldValue == null)
            return super.put(key, value);
        return super.put(key, oldValue   value);
    }
}
 

Заранее спасибо

Дополнительная информация:

  • Я хочу использовать функцию putAll() для сокращения потока из пользовательских хэш-карт.
  • Если у меня есть две пользовательские хэш-карты такого рода {"key1" : 2, "key3" : 4} , и {"key3" : 1} результат a.putAll(b) должен быть {"key1" : 2, "key3" : 5}

Комментарии:

1. Вместо создания такой структуры данных сделайте эту put функцию статической вспомогательной функцией. Структуры данных сделаны универсальными. Это было бы невозможно поддерживать, когда вам нужно будет создать новый класс hashmap с небольшим изменением.

Ответ №1:

Для этого вам не нужна новая структура данных, вам даже не нужен новый класс, который наследуется от HashMap . Вместо этого используйте Map.merge метод:

 newMap.forEach((k, v) -> oldMap.merge(k, v, Integer::sum));
 

Этот код используется Map.forEach для обхода записей новой карты (той, которую вы получите в качестве аргумента putAll ) и использует Map.merge (вместе с Integer::sum ) для объединения своих записей в уже существующую карту (которую я назвал oldMap здесь).

Комментарии:

1. Если вы используете ConcurrentHashMap , то один merge будет потокобезопасным без каких-либо дополнительных усилий. Неясно, является ли безопасность потоков (подразумеваемым) требованием.

Ответ №2:

Я думаю, это то, что вы ищете. Я сделал так, чтобы ключ мог быть любого типа. Если вы хотите, вы можете удалить generic для ключа и просто расширить HashMap<Строка, целое число> .

 import java.util.HashMap;
import java.util.Map;

public class AddingHashMap<K> extends HashMap<K, Integer> {
    @Override
    public Integer put(K key, Integer value) {
        Integer existingValue = super.get(key);
        if (existingValue == null) {
            existingValue = value;
        } else {
            existingValue = existingValue.intValue()   value.intValue();
        }
        return super.put(key, existingValue);
    }

    @Override
    public void putAll(Map<? extends K, ? extends Integer> m) {
        m.entrySet().forEach(entry -> {
            this.put(entry.getKey(), entry.getValue());
        });
    }
}
 

Вот это работает:

 public static void main(String[] argv) {
        AddingHashMap<String> myAddingHashMap = new AddingHashMap<>();
        myAddingHashMap.put("One", 1);
        myAddingHashMap.put("Two", 2);
        myAddingHashMap.put("One", 3);

        myAddingHashMap.entrySet().forEach(entry -> System.out.println(entry.getKey()   " - "   entry.getValue()));
    }
 

Выводит:

 One - 4
Two - 2
 

Последующее редактирование: имейте в виду, что это НЕ является потокобезопасным.

Комментарии:

1. Спасибо, ваш элегантный способ переопределения putAll() был даже больше, чем я надеялся! Работает как шарм 🙂

2. @doej1367 Рад, что смог помочь. Имейте в виду, что это просто быстрая реализация, чтобы показать направление. Это не потокобезопасно, оно не обрабатывает значения null и могут быть другие проблемы. Кроме того, дайте мне знать, если вам нужны какие-либо разъяснения по реализации.

3. 1) насколько я помню, что означает безопасность потоков, это довольно большая тема. В этом случае это только тот порядок, который немного перепутан, или это может вызвать какие-то другие проблемы, которые я пока не вижу… например, если я использую parallel() метод в своем потоке, где я использую putAll() его для уменьшения — это может быть неприятно. Но я этого не делаю, так что все должно быть хорошо, верно? 2) значения null обрабатываются в другой точке моего кода и / или отсутствуют в наборе данных, так что все в порядке. Спасибо, что упомянул об этом.

4. Подклассы HashMap не являются ни необходимыми, ни желательными. В java есть много методов, Map interface которые облегчают этот тип требований.

5. @WJS Почему это нежелательно? Тот факт, что OP нуждается в методах putAll(), является признаком того, что он уже использует HashMaps для хранения необработанных данных. Расширяя его, вы получаете все необходимые функциональные возможности, к которым он привык. Ему все равно пришлось бы использовать HashMap внутри для хранения обработанных данных, но с расширением нового класса HashMap вам не нужно усложнять, а переписывать некоторые методы, такие как get() .

Ответ №3:

Я не думаю, что существует структура данных, которая это делает. Целью структуры данных является хранение данных, а не привязка к ним логики. HashMap может хранить пары ключ-значение для вас, но если вам нужна более продвинутая или специфическая логика, связанная с определенными операциями, вам нужно будет добавить ее самостоятельно.

Один из способов — обернуть карту в класс, который имеет эту логику. Другим может быть самостоятельное внедрение интерфейса Map (который также может использовать HashMap внутри), хотя я бы не рекомендовал этого, поскольку изменение поведения не является отличной идеей.

Минимальная оболочка, обеспечивающая добавление функциональности:

 public class AddingMap {
    private final HashMap<String, Integer> map;

    public AddingMap() {
        map = new HashMap<>();
    }

    public void add(String key, Integer value) {
        map.put(key, map.getOrDefault(key, 0)   value);
    }
    
    public Integer get(String key) {
        return map.get(key);
    }
}
 

Редактировать

Не следовало заканчивать написание ответа на полпути…

Действительно, метод addAll() отсутствует:

 public void addAll(Map<String, Integer> map) {
    map.entrySet().forEach(e -> this.add(e.getKey(), e.getValue()));
}
 

Комментарии:

1. Спасибо, за быстрый ответ. Уже думал о функции-оболочке, но что произойдет, если я вызову putAll() для этой функции? Разве это не заменит значения снова?

2. Это не то, о чем спрашивал OP. При использовании этой реализации проблема с putAll() все еще сохраняется.