#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() все еще сохраняется.