Надеюсь, этот вопрос не считается слишком простым для этого форума, но посмотрим. Мне интересно, как реорганизовать код для повышения производительности, который запускается много раз.
Скажем, я создаю список частотности слов, используя карту (возможно, HashMap), где каждый ключ представляет собой строку с подсчитываемым словом, а значение представляет собой целое число, которое увеличивается каждый раз, когда обнаруживается токен слова.
В Perl увеличить такое значение было бы тривиально просто:
$map{$word}++;
Но в Java все намного сложнее. Вот как я это делаю сейчас:
int count = map.containsKey(word) ? map.get(word) : 0;
map.put(word, count + 1);
Что, конечно, зависит от функции автобокса в новых версиях Java. Интересно, можете ли вы предложить более эффективный способ увеличения такого значения. Есть ли даже веские причины для повышения производительности отказаться от фреймворка Collections и вместо этого использовать что-то другое?
Обновление: я проверил несколько ответов. Смотри ниже.
Конечно, если бы было то же самое, потому что Hashtable - это фактически карта.
Java 8: пример computeIfAbsent: stackoverflow.com/a/37439971/1216775
Я думаю, что ваше решение будет стандартным, но, как вы сами заметили, это, вероятно, не самый быстрый способ.
Вы можете посмотреть GNU Trove. Это библиотека, которая содержит все виды быстрых примитивных коллекций. В вашем примере будет использоваться TObjectIntHashMap, у которого есть метод adjustOrPutValue, который делает именно то, что вы хотите.
Ссылка на TObjectIntHashMap не работает. Это правильная ссылка: trove4j.sourceforge.net/javadocs/gnu/trove/map/…
Спасибо, Эрель, ссылку исправил.
Различные примитивные оболочки, например Integer
, неизменяемы, поэтому на самом деле нет более лаконичного способа сделать то, что вы просите пока не, вы можете сделать это с помощью чего-то вроде AtomicLong. Я могу попробовать через минуту и обновить. Кстати, Хеш-таблицаявляется является частью Фреймворк коллекций.
Есть несколько подходов:
Используйте алоритм сумок, как наборы, содержащиеся в коллекциях Google.
Создайте изменяемый контейнер, который вы можете использовать на карте:
class My{
String word;
int count;
}
И используйте put ("слово", новое My ("Слово")); Затем вы можете проверить, существует ли он, и увеличить его при добавлении.
Избегайте развертывания собственного решения с использованием списков, потому что, если вы получите поиск и сортировку по внутреннему циклу, ваша производительность будет вонять. Первое решение HashMap на самом деле довольно быстрое, но подходящее, подобное тому, что можно найти в Google Collections, вероятно, лучше.
Подсчет слов с помощью Google Collections выглядит примерно так:
HashMultiset s = new HashMultiset();
s.add("word");
s.add("word");
System.out.println(""+s.count("word") );
Использование HashMultiset довольно элегантно, потому что алгоритм сумок - это именно то, что вам нужно при подсчете слов.
В продолжение моего собственного комментария: Trove выглядит правильным решением. Если по какой-либо причине вы хотели придерживаться стандартного JDK, ConcurrentMap и AtomicLong могут сделать код более приятным на бит крошечный, хотя YMMV.
final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
map.putIfAbsent("foo", new AtomicLong(0));
map.get("foo").incrementAndGet();
оставит 1
в качестве значения на карте для foo
. На самом деле, этот подход может рекомендовать только повышенное удобство использования потоков.
PutIfAbsent () возвращает значение. Было бы большим улучшением сохранить возвращаемое значение в локальной переменной и использовать его для incrementAndGet () вместо повторного вызова get.
putIfAbsent может возвращать нулевое значение, если указанный ключ еще не связан со значением внутри Map, поэтому я бы с осторожностью использовал возвращаемое значение. docs.oracle.com/javase/8/docs/api/java/util/…
Другой способ - создать изменяемое целое число:
class MutableInt {
int value = 0;
public void inc () { ++value; }
public int get () { return value; }
}
...
Map<String,MutableInt> map = new HashMap<String,MutableInt> ();
MutableInt value = map.get (key);
if (value == null) {
value = new MutableInt ();
map.put (key, value);
} else {
value.inc ();
}
конечно, это подразумевает создание дополнительного объекта, но накладные расходы по сравнению с созданием Integer (даже с Integer.valueOf) не должны быть такими большими.
Вы не хотите запускать MutableInt с 1 при первом размещении на карте?
На общедоступном языке Apache уже написан MutableInt.
Вместо вызова containsKey () быстрее просто вызвать map.get и проверить, является ли возвращаемое значение нулевым или нет.
Integer count = map.get(word);
if (count == null){
count = 0;
}
map.put(word, count + 1);
Вы должны знать, что ваша первоначальная попытка
int count = map.containsKey(word) ? map.get(word) : 0;
содержит две потенциально дорогостоящие операции на карте, а именно containsKey
и get
. Первый выполняет операцию, потенциально очень похожую на последнюю, так что вы выполняете ту же работу дважды!
Если вы посмотрите на API для карты, операции get
обычно возвращают null
, когда карта не содержит запрошенный элемент.
Обратите внимание, что это приведет к решению вроде
map.put( key, map.get(key) + 1 );
опасно, так как это может привести к выходу NullPointerException
. Вы должны сначала проверить null
.
Также обратите внимание, и это очень важно, чтобы HashMap
может содержал nulls
по определению. Так что не каждый возвращенный null
говорит, что «такого элемента нет». В этом отношении containsKey
ведет себя как иначе от get
, фактически сообщая вам, что ли существует. См. Подробности в API.
В вашем случае, однако, вы можете не захотеть различать сохраненный null
и «noSuchElement». Если вы не хотите разрешать null
, вы можете предпочесть Hashtable
. Использование библиотеки-оболочки, как уже предлагалось в других ответах, может быть лучшим решением для ручной обработки, в зависимости от сложности вашего приложения.
Чтобы завершить ответ (и я забыл сначала вставить это, благодаря функции редактирования!), Лучший способ сделать это изначально - это включить get
в переменную final
, проверить null
и put
обратно с помощью 1
. Переменная должна быть final
, потому что она в любом случае неизменна. Компилятору эта подсказка может и не понадобиться, но так она будет понятнее.
final HashMap map = generateRandomHashMap(); final Object key = fetchSomeKey(); final Integer i = map.get(key); if (i != null) { map.put(i + 1); } else { // do something }
Если вы не хотите полагаться на автобоксинг, вам следует вместо этого сказать что-то вроде map.put(new Integer(1 + i.getValue()));
.
Чтобы избежать проблемы с начальными несопоставленными / нулевыми значениями в Groovy, я делаю: counts.put (key, (counts.get (key)?: 0) + 1) // слишком сложная версия ++
Или, что проще всего: counts = [:]. WithDefault {0} // ++ прочь
Я бы использовал ленивую карту Apache Collections Lazy Map (для инициализации значений 0) и использовал MutableIntegers из Apache Lang в качестве значений на этой карте.
Самая большая стоимость - это необходимость дважды просмотреть карту в вашем методе. В моем случае вы должны сделать это только один раз. Просто получите значение (оно будет инициализировано, если оно отсутствует) и увеличьте его.
Вы уверены, что это узкое место? Вы делали какой-нибудь анализ производительности?
Попробуйте использовать профилировщик NetBeans (бесплатный и встроенный в NB 6.1), чтобы посмотреть на точки доступа.
Наконец, обновление JVM (скажем, с 1.5 до 1.6) часто является дешевым средством повышения производительности. Даже обновление номера сборки может обеспечить хороший прирост производительности. Если вы работаете в Windows и это приложение серверного класса, используйте -server в командной строке, чтобы использовать JVM Server Hotspot. На машинах Linux и Solaris это определяется автоматически.
@Vilmantas Baranauskas: Что касается этого ответа, я бы прокомментировал, если бы у меня были очки репутации, но у меня их нет. Я хотел отметить, что определенный там класс Counter НЕ является потокобезопасным, поскольку недостаточно просто синхронизировать inc () без синхронизации value (). Не гарантируется, что другие потоки, вызывающие value (), увидят это значение, если с обновлением не была установлена связь «произошло до того».
Если вы хотите сослаться на чей-то ответ, используйте @ [Имя пользователя] вверху, например, @Vilmantas Baranauskas <Содержимое здесь>
Я сделал это изменение, чтобы очистить его.
Ротация памяти может быть здесь проблемой, поскольку каждая упаковка int, большего или равного 128, вызывает выделение объекта (см. Integer.valueOf (int)). Хотя сборщик мусора очень эффективно работает с недолговечными объектами, производительность в некоторой степени пострадает.
Если вы знаете, что количество сделанных приращений будет в значительной степени превышать количество ключей (= слов в данном случае), рассмотрите возможность использования вместо этого держателя int. Phax уже представил для этого код. Вот он снова, с двумя изменениями (класс держателя стал статическим, а начальное значение установлено на 1):
static class MutableInt {
int value = 1;
void inc() { ++value; }
int get() { return value; }
}
...
Map<String,MutableInt> map = new HashMap<String,MutableInt>();
MutableInt value = map.get(key);
if (value == null) {
value = new MutableInt();
map.put(key, value);
} else {
value.inc();
}
Если вам нужна максимальная производительность, поищите реализацию Map, которая напрямую адаптирована к примитивным типам значений. Джрудольф упомянул GNU Trove.
Кстати, хороший поисковый запрос по этой теме - «гистограмма».
Всегда полезно посмотреть на Библиотека коллекций Google для такого рода вещей. В этом случае Мультимножество сделает свое дело:
Multiset bag = Multisets.newHashMultiset();
String word = "foo";
bag.add(word);
bag.add(word);
System.out.println(bag.count(word)); // Prints 2
Существуют методы типа Map для перебора ключей / записей и т. д. Внутренняя реализация в настоящее время использует HashMap<E, AtomicInteger>
, поэтому вы не понесете затрат на упаковку.
Приведенный выше ответ должен отражать ответ компании. API изменился с момента его публикации (3 года назад :))
Выполняется ли метод count()
в мультимножестве за время O (1) или O (n) (наихудший случай)? Документы неясны по этому поводу.
Мой алгоритм для такого рода вещей: if (hasApacheLib (thing)) return apacheLib; иначе, если (hasOnGuava (thing)) вернет гуаву. Обычно я не могу пройти эти два шага. :)
Я получил много хороших ответов на этот вопрос - спасибо, ребята, - поэтому я решил провести несколько тестов и выяснить, какой метод на самом деле самый быстрый. Я тестировал следующие пять методов:
Вот что я сделал ...
Сначала я представлю результаты и приведенный ниже код для тех, кому это интересно.
Метод ContainsKey, как и ожидалось, был самым медленным, поэтому я приведу скорость каждого метода в сравнении со скоростью этого метода.
Похоже, что только метод MutableInt и метод Trove значительно быстрее, поскольку только они дают прирост производительности более чем на 10%. Однако, если многопоточность является проблемой, AtomicLong может быть более привлекательным, чем другие (я не совсем уверен). Я также запускал TestForNull с переменными final
, но разница была незначительной.
Обратите внимание, что я не профилировал использование памяти в различных сценариях. Я был бы рад услышать мнение любого, кто хорошо понимает, как методы MutableInt и Trove могут повлиять на использование памяти.
Лично я считаю метод MutableInt наиболее привлекательным, поскольку он не требует загрузки каких-либо сторонних классов. Так что, если я не обнаружу проблем с этим, я, скорее всего, пойду именно так.
Вот ключевой код каждого метода.
import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
int count = freq.containsKey(word) ? freq.get(word) : 0;
freq.put(word, count + 1);
import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
Integer count = freq.get(word);
if (count == null) {
freq.put(word, 1);
}
else {
freq.put(word, count + 1);
}
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
...
final ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
...
map.putIfAbsent(word, new AtomicLong(0));
map.get(word).incrementAndGet();
import gnu.trove.TObjectIntHashMap;
...
TObjectIntHashMap<String> freq = new TObjectIntHashMap<String>();
...
freq.adjustOrPutValue(word, 1, 1);
import java.util.HashMap;
import java.util.Map;
...
class MutableInt {
int value = 1; // note that we start at 1 since we're counting
public void increment () { ++value; }
public int get () { return value; }
}
...
Map<String, MutableInt> freq = new HashMap<String, MutableInt>();
...
MutableInt count = freq.get(word);
if (count == null) {
freq.put(word, new MutableInt());
}
else {
count.increment();
}
Отличная работа, молодец. Небольшой комментарий - вызов putIfAbsent () в коде AtomicLong создаст экземпляр нового AtomicLong (0), даже если он уже есть на карте. Если вы настроите это, чтобы вместо этого использовать if (map.get (key) == null), вы, вероятно, получите улучшение результатов этих тестов.
Недавно я сделал то же самое с подходом, похожим на MutableInt. Я рад слышать, что это оптимальное решение (я просто предположил, что это так, не проводя никаких тестов).
Я хотел добавить, что функция приращения в Trove - действительно удобная функция. Вы также должны изучить методы map.apply (procedure) в хранилище. Основная проблема - это автобокс и двойной поиск, когда метод apply исходит из функционального программирования (например, Lisp).
Хотя метод приращения MutableInt очень быстр, он не является потокобезопасным в целом, поскольку оператор ++ не является атомарным.
В случае Atomic Long не было бы более эффективным сделать это за один шаг (так что у вас есть только 1 дорогостоящая операция get вместо 2) "map.putIfAbsent (word, new AtomicLong (0)). IncrementAndGet ();"
@ smartnut007 map.putIfAbsent возвращает предыдущее значение, которое будет нулевым при первом доступе, вызывая исключение нулевого указателя.
Это несовершеннолетний. Это должно было быть «temp = new AtomicLong (1); prev = map.putIfAbsent (word, temp); if (prev! = Null) prev.incrementAndGet ();»
Вы здесь не сравниваете яблоки с яблоками. Ни один из других вариантов не является потокобезопасным, поэтому в версии Atomic для равного сравнения следует использовать обычный HashMap
, а не ConcurrentHashMap
. Это также должен быть AtomicInteger
, а не AtomicLong
, опять же для равного сравнения. --- Кроме того, int[1]
будет простой встроенной версией MutableInt
, не требующей нового класса.
@gregory вы считали freq.compute(word, (key, count) -> count == null ? 1 : count + 1)
Java 8? Внутренне он выполняет на один хешированный поиск меньше, чем containsKey
, было бы интересно посмотреть, как он сравнивается с другими из-за лямбда.
Вы также можете сделать freq.merge(word, 1, Integer::sum)
Не могли бы вы добавить Map.merge в тест или предоставить полный исходный код, чтобы я мог протестировать?
В структуре данных TreeMap
библиотеки Функциональная Java есть метод update
в последней головной части ствола:
public TreeMap<K, V> update(final K k, final F<V, V> f)
Пример использования:
import static fj.data.TreeMap.empty;
import static fj.function.Integers.add;
import static fj.pre.Ord.stringOrd;
import fj.data.TreeMap;
public class TreeMap_Update
{public static void main(String[] a)
{TreeMap<String, Integer> map = empty(stringOrd);
map = map.set("foo", 1);
map = map.update("foo", add.f(1));
System.out.println(map.get("foo").some());}}
Эта программа печатает «2».
"положить" нужно "получить" (чтобы не повторялся ключ) .
Так прямо делай "ставь",
и если было предыдущее значение, сделайте сложение:
Map map = new HashMap ();
MutableInt newValue = new MutableInt (1); // default = inc
MutableInt oldValue = map.put (key, newValue);
if (oldValue != null) {
newValue.add(oldValue); // old + inc
}
Если счетчик начинается с 0, добавьте 1: (или любые другие значения ...)
Map map = new HashMap ();
MutableInt newValue = new MutableInt (0); // default
MutableInt oldValue = map.put (key, newValue);
if (oldValue != null) {
newValue.setValue(oldValue + 1); // old + inc
}
Уведомление : Этот код не является потокобезопасным. Используйте его для построения, а затем используйте карту, а не для ее одновременного обновления.
Оптимизация: В цикле сохранить старое значение, чтобы оно стало новым значением следующего цикла.
Map map = new HashMap ();
final int defaut = 0;
final int inc = 1;
MutableInt oldValue = new MutableInt (default);
while(true) {
MutableInt newValue = oldValue;
oldValue = map.put (key, newValue); // insert or...
if (oldValue != null) {
newValue.setValue(oldValue + inc); // ...update
oldValue.setValue(default); // reuse
} else
oldValue = new MutableInt (default); // renew
}
}
Коллекции Google HashMultiset:
- довольно элегантен в использовании
- но потребляют процессор и память
Лучше всего иметь такой метод, как: Entry<K,V> getOrPut(K);
(элегантный и недорогой)
Такой метод будет вычислять хэш и индекс только один раз, и тогда мы могли бы делать все, что захотим, с записью (либо замените, либо обновите значение).
Более элегантный:
- берем HashSet<Entry>
- расширить его так, чтобы get(K)
при необходимости поместил новую запись
- Запись может быть вашим собственным объектом.
-> (new MyHashSet()).get(k).increment();
Вариант подхода MutableInt, который может быть даже быстрее, если немного взломать, заключается в использовании одноэлементного массива int:
Map<String,int[]> map = new HashMap<String,int[]>();
...
int[] value = map.get(key);
if (value == null)
map.put(key, new int[]{1} );
else
++value[0];
Было бы интересно, если бы вы могли повторно запустить тесты производительности с этим вариантом. Это могло быть самым быстрым.
Обновлено: приведенный выше шаблон отлично работал у меня, но в конце концов я перешел на использование коллекций Trove, чтобы уменьшить объем памяти на некоторых очень больших картах, которые я создавал - и в качестве бонуса он был быстрее.
Одна действительно приятная особенность заключается в том, что у класса TObjectIntHashMap
есть единственный вызов adjustOrPutValue
, который, в зависимости от того, есть ли уже значение в этом ключе, либо помещает начальное значение, либо увеличивает существующее значение. Это идеально подходит для увеличения:
TObjectIntHashMap<String> map = new TObjectIntHashMap<String>();
...
map.adjustOrPutValue(key, 1, 1);
... по крайней мере, в некоторых случаях. У них есть этот красивый AtomicLongMap. Особенно приятно, потому что вы имеете дело с длинный в качестве значения на вашей карте.
Например.
AtomicLongMap<String> map = AtomicLongMap.create();
[...]
map.getAndIncrement(word);
Также можно добавить к значению больше 1:
map.getAndAdd(word, 112L);
AtomicLongMap#getAndAdd
принимает примитив long
, а не класс оболочки; нет смысла делать new Long()
. А AtomicLongMap
- это параметризованный тип; вы должны были указать его как AtomicLongMap<String>
.
Если вы используете Коллекции Eclipse, вы можете использовать HashBag
. Это будет наиболее эффективный подход с точки зрения использования памяти, а также он будет хорошо работать с точки зрения скорости выполнения.
HashBag
поддерживается MutableObjectIntMap
, который хранит примитивные целые числа вместо объектов Counter
. Это снижает накладные расходы на память и повышает скорость выполнения.
HashBag
предоставляет API, который вам может понадобиться, поскольку это Collection
, который также позволяет вам запрашивать количество вхождений элемента.
Вот пример из Ката Коллекции Затмения.
MutableBag<String> bag =
HashBag.newBagWith("one", "two", "two", "three", "three", "three");
Assert.assertEquals(3, bag.occurrencesOf("three"));
bag.add("one");
Assert.assertEquals(2, bag.occurrencesOf("one"));
bag.addOccurrences("one", 4);
Assert.assertEquals(6, bag.occurrencesOf("one"));
Примечание: Я участник коллекций Eclipse.
Небольшое исследование в 2016 году: https://github.com/leventov/java-word-count, исходный код теста
Лучшие результаты для каждого метода (чем меньше, тем лучше):
time, ms
kolobokeCompile 18.8
koloboke 19.8
trove 20.8
fastutil 22.7
mutableInt 24.3
atomicInteger 25.3
eclipse 26.9
hashMap 28.0
hppc 33.6
hppcRt 36.5
Результаты по времени \ пространству:
Спасибо, это было действительно полезно. Было бы здорово добавить в тест Guava Multiset (например, HashMultiset).
Map<String, Integer> map = new HashMap<>();
String key = "a random key";
int count = map.getOrDefault(key, 0); // ensure count will be one of 0,1,2,3,...
map.put(key, count + 1);
И вот как вы увеличиваете значение с помощью простого кода.
Выгода:
Обратная сторона:
Теоретически, как только вы вызываете get (), вы уже знаете, куда поместить (), поэтому вам не придется снова искать. Но поиск в хэш-карте обычно занимает очень минимальное время, поэтому вы можете игнорировать эту проблему с производительностью.
Но если вы очень серьезно относитесь к проблеме, вы перфекционист, другой способ - использовать метод слияния, это (вероятно) более эффективно, чем предыдущий фрагмент кода, поскольку вы (теоретически) будете искать карту только один раз: (хотя этот код неочевиден с первого взгляда, он короткий и производительный)
map.merge(key, 1, (a,b) -> a+b);
Предложение: большую часть времени вы должны заботиться о читабельности кода, а не о небольшом приросте производительности. Если первый фрагмент кода вам легче понять, используйте его. Но если ты понимаешь и второй штраф, то можешь пойти и на это!
Метод getOfDefault недоступен в JAVA 7. Как добиться этого в JAVA 7?
Тогда вам, возможно, придется полагаться на другие ответы. Это работает только в Java 8.
+1 для решения слияния, это будет самая эффективная функция, потому что вам нужно заплатить только 1 раз за расчет хэш-кода (в случае, если карта, на которой вы ее используете, правильно поддерживает метод), вместо того, чтобы потенциально платить за него 3 раз
Использование вывода метода: map.merge (key, 1, Integer :: sum)
Я не знаю, насколько это эффективно, но приведенный ниже код тоже работает. Вам нужно сначала определить BiFunction
. Кроме того, с помощью этого метода вы можете сделать больше, чем просто увеличить.
public static Map<String, Integer> strInt = new HashMap<String, Integer>();
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> bi = (x,y) -> {
if (x == null)
return y;
return x+y;
};
strInt.put("abc", 0);
strInt.merge("abc", 1, bi);
strInt.merge("abc", 1, bi);
strInt.merge("abc", 1, bi);
strInt.merge("abcd", 1, bi);
System.out.println(strInt.get("abc"));
System.out.println(strInt.get("abcd"));
}
выход
3
1
Вы можете использовать метод computeIfAbsent в интерфейсе Map
, предусмотренном в Java 8.
final Map<String,AtomicLong> map = new ConcurrentHashMap<>();
map.computeIfAbsent("A", k->new AtomicLong(0)).incrementAndGet();
map.computeIfAbsent("B", k->new AtomicLong(0)).incrementAndGet();
map.computeIfAbsent("A", k->new AtomicLong(0)).incrementAndGet(); //[A=2, B=1]
Метод computeIfAbsent
проверяет, связан ли уже указанный ключ со значением или нет? Если связанного значения нет, он пытается вычислить его значение, используя заданную функцию сопоставления. В любом случае он возвращает текущее (существующее или вычисленное) значение, связанное с указанным ключом, или null, если вычисленное значение равно null.
С другой стороны, если у вас есть ситуация, когда несколько потоков обновляют общую сумму, вы можете взглянуть на класс LongAdder. В условиях высокой конкуренции ожидаемая пропускная способность этого класса значительно выше, чем AtomicLong
, за счет более высокого потребления места.
почему concurrentHashmap и AtomicLong?
Теперь есть более короткий путь с Java 8 с использованием Map::merge
.
myMap.merge(key, 1, Integer::sum)
Что оно делает:
Дополнительная информация здесь.
всегда люблю java 8. Это атомарно? или я должен окружить его синхронизированным?
это, похоже, не сработало для меня, но map.merge(key, 1, (a, b) -> a + b);
сработал
@Tiina Характеристики атомарности зависят от реализации, см. документы: «Реализация по умолчанию не дает никаких гарантий относительно свойств синхронизации или атомарности этого метода. Любая реализация, обеспечивающая гарантии атомарности, должна переопределить этот метод и задокументировать его свойства параллелизма. В частности, все реализации субинтерфейса ConcurrentMap должны задокументировать, применяется ли функция один раз атомарно. только если значение отсутствует ".
Для groovy он не принимал Integer::sum
как BiFunction, и ему не нравился ответ @russter в том виде, в каком он был написан. Это сработало для меня Map.merge(key, 1, { a, b -> a + b})
@russter, я знаю, что ваш комментарий был больше года назад, но вы случайно не помните, почему он у вас не сработал? Вы получили ошибку компиляции или значение не увеличивалось?
@Tiina Для параллелизма выберите реализацию Map
, которая реализует ConcurrentMap
. В комплекте с Java поставляются два: ConcurrentSkipList
и ConcurrentHashMap
.
безупречный ответ
В этом методе используются целые числа в рамке, что означает, что повторное суммирование приведет к созданию объекта N
, а не к изменению той же переменной int
. Есть варианты получше по производительности.
Поскольку многие люди ищут в темах Java ответы на Groovy, вот как это можно сделать в Groovy:
dev map = new HashMap<String, Integer>()
map.put("key1", 3)
map.merge("key1", 1) {a, b -> a + b}
map.merge("key2", 1) {a, b -> a + b}
Надеюсь, я правильно понимаю ваш вопрос, я перехожу на Java с Python, поэтому я могу посочувствовать вашей борьбе.
если у вас есть
map.put(key, 1)
ты бы сделал
map.put(key, map.get(key) + 1)
Надеюсь это поможет!
Довольно просто, просто используйте встроенную функцию в Map.java
, как показано ниже.
map.put(key, map.getOrDefault(key, 0) + 1);
Это не увеличивает значение, а просто устанавливает текущее значение или 0, если ключу не было присвоено значение.
Вы можете увеличить значение на ++
... Боже, это так просто. @siegi
Для записи: ++
нигде в этом выражении не работает, потому что переменная необходима в качестве его операнда, но есть только значения. Однако ваше добавление + 1
работает. Теперь ваше решение такое же, как в off99555s ответ.
Простой и легкий способ в java 8 заключается в следующем:
final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
map.computeIfAbsent("foo", key -> new AtomicLong(0)).incrementAndGet();
Предлагаю использовать Java 8 Map :: compute (). Рассматривается и случай, когда ключа не существует.
Map.compute(num, (k, v) -> (v == null) ? 1 : v + 1);
mymap.merge(key, 1, Integer::sum)
?
Думаю, то же самое и с java.util.Hashtable.