Java에서 맵 값을 증가시키는 가장 효율적인 방법


377

이 질문이이 포럼에서 너무 기본적이지 않기를 바랍니다. 그러나 우리는 보게 될 것입니다. 여러 번 실행되는 더 나은 성능을 위해 일부 코드를 리팩터링하는 방법이 궁금합니다.

Map (아마 HashMap)을 사용하여 단어 빈도 목록을 작성한다고 가정하십시오. 여기서 각 키는 계산되는 단어가있는 문자열이고 값은 단어의 토큰을 찾을 때마다 증가하는 정수입니다.

Perl에서 그러한 값을 증가시키는 것은 사소한 일입니다.

$map{$word}++;

그러나 Java에서는 훨씬 더 복잡합니다. 여기 내가 현재하고있는 방식 :

int count = map.containsKey(word) ? map.get(word) : 0;
map.put(word, count + 1);

최신 Java 버전의 오토 박싱 기능에 의존하는 것은 물론입니다. 그러한 가치를 높이는보다 효율적인 방법을 제안 할 수 있는지 궁금합니다. Collections 프레임 워크를 피하고 대신 다른 것을 사용하는 좋은 성능 이유가 있습니까?

업데이트 : 몇 가지 답변을 테스트했습니다. 아래를 참조하십시오.


java.util.Hashtable과 동일하다고 생각합니다.
jrudolph

2
물론 Hashtable은 실제로 Map이기 때문에 동일 할 것입니다.
위스키 시에라

Java 8 : computeIfAbsent 예제 : stackoverflow.com/a/37439971/1216775
akhil_mittal 4

답변:


366

일부 테스트 결과

나는이 질문에 많은 좋은 답변을 얻었습니다. 감사합니다. 그래서 몇 가지 테스트를 실행하고 실제로 가장 빠른 방법을 결정하기로 결정했습니다. 내가 테스트 한 5 가지 방법은 다음과 같습니다.

  • 질문에 제시 한 "ContainsKey"방법
  • Aleksandar Dimitrov가 제안한 "TestForNull"메소드
  • Hank Gay가 제안한 "AtomicLong"방법
  • jrudolph가 제안한 "Trove"방법
  • phax.myopenid.com에서 제안한 "MutableInt"방법

방법

여기 내가 한 일이 있습니다 ...

  1. 아래 표시된 차이점을 제외하고 동일한 5 개의 클래스를 작성했습니다. 각 클래스는 내가 제시 한 시나리오, 즉 10MB 파일을 열고 읽은 다음 파일의 모든 단어 토큰의 빈도 수를 수행하는 일반적인 작업을 수행해야했습니다. 이 작업에는 평균 3 초 밖에 걸리지 않았으므로 I / O가 아닌 주파수 카운트를 10 회 수행했습니다.
  2. 10 회 반복을 반복했지만 I / O 작업은 수행하지 않았 으며 Java Cookbook에서 Ian Darwin의 방법을 사용하여 취한 총 시간 (시계 초)을 기록했습니다 .
  3. 다섯 가지 테스트를 모두 연속으로 수행 한 다음이 작업을 세 번 더 수행했습니다.
  4. 각 방법에 대해 4 개의 결과를 평균했습니다.

결과

먼저 관심있는 사람들을 위해 결과와 아래 코드를 제시하겠습니다.

ContainsKey의 나는 그 방법의 속도에 비해 각 방법의 속도를 줄 것이다, 그래서 방법은, 가장 느린을 예상한다.

  • ContainsKey : 30.654 초 (기준)
  • 원자 길이 : 29.780 초 (1.03 배 빠른 속도)
  • TestForNull : 28.804 초 (1.06 배 빠른 속도)
  • 로브 : 26.313 초 (1.16 배 빠른 속도)
  • MutableInt : 25.747 초 (1.19 배 빠른 속도)

결론

MutableInt 방법과 Trove 방법 만 10 % 이상의 성능 향상을 제공한다는 점에서 훨씬 빠릅니다. 그러나 스레딩이 문제인 경우 AtomicLong이 다른 것보다 매력적일 수 있습니다 (확실하지는 않습니다). final변수로 TestForNull을 실행 했지만 그 차이는 무시할 만했습니다.

다른 시나리오에서 메모리 사용량을 프로파일 링하지 않았습니다. MutableInt 및 Trove 메서드가 메모리 사용에 어떤 영향을 미칠지에 대한 통찰력이있는 사람이라면 누구나 기뻐할 것입니다.

개인적으로 MutableInt 메서드는 타사 클래스를로드 할 필요가 없으므로 가장 매력적입니다. 따라서 문제를 발견하지 않으면 내가 갈 가능성이 가장 큽니다.

코드

각 방법의 중요한 코드는 다음과 같습니다.

ContainsKey

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

TestForNull

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

MutableInt

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

3
잘 했어. 사소한 설명-AtomicLong 코드의 putIfAbsent () 호출은 맵에 이미있는 경우에도 새로운 AtomicLong (0)을 인스턴스화합니다. if (map.get (key) == null) 대신 사용하도록 이것을 조정하면 테스트 결과가 향상 될 것입니다.
레이 콜드웰

2
MutableInt와 비슷한 접근 방식으로 최근에 동일한 작업을 수행했습니다. 최적의 솔루션이라고 들었습니다 (테스트를하지 않고 방금 가정했습니다).
Kip

당신이 나보다 빠르다는 소식을 듣게되어 반갑습니다, 킵 ;-) 그 접근법에 대한 단점을 발견하면 알려주십시오.
gregory

4
원자 긴 경우에는 한 단계로 수행하는 것이 더 효율적이지 않을 것입니다 (따라서 2 대신 1 개의 비싼 가져 오기 작업 만 있음) "map.putIfAbsent (word, new AtomicLong (0)). incrementAndGet ();"
smartnut007

1
@gregory Java 8을 고려 했 freq.compute(word, (key, count) -> count == null ? 1 : count + 1)습니까? 내부적으로는 하나보다 덜 해시 조회 containsKey를 수행합니다. 람다 때문에 다른 것과 비교하는 방법을 보는 것이 흥미로울 것입니다.
TWiStErRob

255

이제 Java 8을 사용하는 더 짧은 방법이 Map::merge있습니다.

myMap.merge(key, 1, Integer::sum)

그것이하는 일 :

  • 경우 키가 존재하지 않습니다 넣어 1 값으로
  • 그렇지 않으면 1키에 연결된 값으로 합산

자세한 내용은 여기를 참조 하십시오 .


항상 Java 8을 좋아합니다.이 원자입니까? 또는 동기화 된 주변을 둘러싸 야합니까?
Tiina

4
이하지 않았다 나를 위해 제대로 작동하지만, map.merge(key, 1, (a, b) -> a + b);
russter

2
@Tiina Atomicity 특성은 구현에 따라 다릅니다 (cf. docs : "기본 구현은이 메소드의 동기화 또는 원 자성 특성에 대해 보증하지 않습니다. 원 자성 보장을 제공하는 모든 구현은이 메소드를 대체하고 동시성 특성을 문서화해야합니다. 특히 하위 인터페이스 ConcurrentMap의 모든 구현은 함수가 한 번 적용되는지 여부를 문서화해야합니다. 원자 적으로 만 값이 존재하지 않는 경우 "
jensgram

2
groovy의 Integer::sum경우 BiFunction으로 허용 되지 않으며 @russter가 작성된 방식에 대한 답변을 좋아하지 않았습니다. 이것은 나를 위해 일했다Map.merge(key, 1, { a, b -> a + b})
jookyone

2
@russter, 나는 당신의 의견이 1 년 전에 끝났음을 알고 있지만 왜 그것이 당신에게 효과가 없었는지 기억하십니까? 컴파일 오류가 발생했거나 값이 증가하지 않았습니까?
Paul

44

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

시간 / 공간 결과 :


2
감사합니다. 정말 도움이되었습니다. Guava의 Multiset (예 : HashMultiset)을 벤치 마크에 추가하는 것이 좋습니다.
cabad

34

Google Guava 는 당신의 친구입니다 ...

... 적어도 경우에 따라. 그들은이 멋진 AtomicLongMap 있습니다. 맵에서 오랫동안 가치를 다루고 있기 때문에 특히 좋습니다 .

예 :

AtomicLongMap<String> map = AtomicLongMap.create();
[...]
map.getAndIncrement(word);

값에 1을 더 추가 할 수도 있습니다.

map.getAndAdd(word, 112L); 

7
AtomicLongMap#getAndAddlong랩퍼 클래스가 아닌 프리미티브 를 사용합니다. 에 아무 소용이 없습니다 new Long(). 그리고 AtomicLongMap매개 변수화 된 유형입니다. 로 선언 했어야합니다 AtomicLongMap<String>.
Helera Pereira

32

@ 행복한 게이

내 자신이 아닌 쓸모없는 의견에 대한 후속 조치로 Trove는 갈 길처럼 보입니다. 어떤 이유로, 당신은 표준 JDK 고수하고 싶었 경우 인 ConcurrentMapAtomicLong는 코드 a를 수있는 작은 비트 좋네요, YMMV하지만.

    final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
    map.putIfAbsent("foo", new AtomicLong(0));
    map.get("foo").incrementAndGet();

1대한 값을지도에 그대로 둡니다 foo. 현실적으로, 스레딩에 대한 친근감 증가는이 접근법이 권장하는 전부입니다.


9
putIfAbsent ()는 값을 반환합니다. 반환 된 값을 로컬 변수에 저장하고 get get을 다시 호출하지 않고 incrementalAndGet ()에 사용하는 것이 크게 향상 될 수 있습니다.
smartnut007

지정된 키가 Map 내부의 값과 아직 연결되어 있지 않은 경우 putIfAbsent는 null 값을 반환 할 수 있으므로 반환 값을 사용하는 데주의해야합니다. docs.oracle.com/javase/8/docs/api/java/util/…
bumbur

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

그리고 이것이 간단한 코드로 값을 증가시키는 방법입니다.

이익:

  • 새로운 클래스를 추가하거나 변경 가능한 int의 다른 개념을 사용할 필요가 없습니다.
  • 라이브러리에 의존하지 않음
  • 정확히 무슨 일이 일어나고 있는지 이해하기 쉽습니다 (너무 추상화되지 않음)

단점 :

  • 해시 맵은 get () 및 put ()에 대해 두 번 검색됩니다. 따라서 가장 성능이 좋은 코드는 아닙니다.

이론적으로 get ()을 호출하면 put () 위치를 이미 알고 있으므로 다시 검색 할 필요가 없습니다. 그러나 해시 맵에서 검색하는 데는 일반적으로이 성능 문제를 무시할 수있는 시간이 매우 짧습니다.

그러나 문제에 대해 매우 진지한 경우 완벽 주의자이며 다른 방법은 병합 방법을 사용하는 것입니다. 이는 이전의 코드 스 니펫보다 (이론적으로) 한 번만 맵을 검색 할 때보 다 효율적입니다. 이 코드는 처음에는 명확하지 않으며 짧고 성능이 좋습니다)

map.merge(key, 1, (a,b) -> a+b);

제안 : 대부분의 시간 동안 성능 향상이 아닌 코드 가독성에주의해야합니다. 첫 번째 코드 스 니펫이 이해하기 쉬운 경우 사용하십시오. 그러나 두 번째 벌금을 이해할 수 있다면 갈 수도 있습니다!


getOfDefault 메소드는 JAVA 7에서 사용할 수 없습니다. JAVA 7에서이를 달성하려면 어떻게해야합니까?
tanvi

1
그러면 다른 답변에 의존해야 할 수도 있습니다. 이것은 Java 8에서만 작동합니다.
off99555

1
병합 솔루션의 경우 +1, 이것은 잠재적으로 지불하는 대신 해시 코드 계산에 1 회만 지불하면되기 때문에 (성능을 사용하는 맵이 메소드를 지원하는 경우) 3 가장 높은 성능을 발휘합니다. 시간
페리 빅

2
메소드 유추 사용 : map.merge (key, 1, Integer :: sum)
earandap

25

이런 종류의 일에 대해서는 항상 Google 컬렉션 라이브러리 를 보는 것이 좋습니다 . 이 경우 멀티 세트 가 트릭을 수행합니다.

Multiset bag = Multisets.newHashMultiset();
String word = "foo";
bag.add(word);
bag.add(word);
System.out.println(bag.count(word)); // Prints 2

키 / 항목 등을 반복하는 맵과 유사한 방법이 있습니다. 내부적으로 구현은 현재을 사용 HashMap<E, AtomicInteger>하므로 권투 비용이 발생하지 않습니다.


위의 답변은 tovares 응답을 반영해야합니다. api는 (3 년 전) 게시 이후로 변경되었습니다
Steve

count()다중 집합 의 메서드가 O (1) 또는 O (n) 시간 (최악)으로 실행됩니까? 이 시점에서 문서가 명확하지 않습니다.
Adam Parkin

이런 종류의 일에 대한 내 알고리즘 : if (hasApacheLib (thing)) return apacheLib; 그렇지 않으면 (hasOnGuava (thing)) 구아바를 반환합니다. 보통 나는이 두 단계를 지나치지 않습니다. :)
digao_mb

22

당신은 당신의 원래 시도가 사실을 알고 있어야

int count = map.containsKey (word)? map.get (word) : 0;

는지도에서 잠재적으로 비싼 두 가지 작업, 즉 containsKey및을 포함 get합니다. 전자는 후자와 거의 비슷한 작업을 수행하므로 동일한 작업을 두 번 수행합니다 !

API for Map을 보면 get일반적으로 null맵에 요청 된 요소가 포함되지 않은 경우 오퍼레이션이 리턴 됩니다.

이것은 같은 해결책을 만들 것입니다

map.put (키, map.get (key) + 1);

위험 할 수 있습니다. NullPointerExceptions를 . null먼저 확인해야합니다 .

또한주의 , 이것은 것이 매우 중요 HashMap수 있습니다 포함 nulls정의. 따라서 모든 반환 된 null"그런 요소가 없다"고 말하는 것은 아닙니다. 이러한 측면에서, containsKey동작 다르게 에서 get실제로 말의 여부 등의 요소가있다. 자세한 내용은 API를 참조하십시오.

그러나 귀하의 경우, 저장된 null것과 "noSuchElement" 를 구별하고 싶지 않을 수 있습니다 . nulls 를 허용하지 않으려면Hashtable . 다른 답변에서 이미 제안 된 래퍼 라이브러리를 사용하면 응용 프로그램의 복잡성에 따라 수동 처리에 더 나은 솔루션이 될 수 있습니다.

답을 완성하려면 (편집 기능 덕분에 처음에는 그것을 잊어 버렸습니다!), 기본적으로 수행하는 가장 좋은 방법은 변수 에 get넣고 final확인 null하고put 는 뒤쪽에와 1. final어쨌든 불변이기 때문에 변수가되어야 합니다. 컴파일러는이 힌트가 필요하지 않을 수도 있지만 그렇게 명확합니다.

최종 해시 맵 맵 = generateRandomHashMap ();
최종 객체 키 = fetchSomeKey ();
최종 정수 i = map.get (key);
if (i! = null) {
    map.put (i + 1);
} else {
    // 무언가를한다
}

오토 박싱에 의존하고 싶지 않다면, map.put(new Integer(1 + i.getValue()));대신 비슷한 말을해야 합니다.


groovy에서 초기 매핑되지 않은 / null 값의 문제를 피하기 위해 counts.put (key, (counts.get (key)? : 0) + 1) // 지나치게 복잡한 ++ 버전
Joe Atzberger

2
또는 가장 간단하게 : counts = [:]. withDefault {0} // ++ away
Joe Atzberger

18

다른 방법은 가변 정수를 만드는 것입니다.

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로도)는 그리 많지 않아야합니다.


5
MutableInt를 처음 맵에 넣을 때 1을 시작하고 싶지 않습니까?
Tom Hawtin-tackline

5
Apache의 commons-lang에는 MutableInt가 이미 작성되어 있습니다.
SingleShot

11

Java 8 에서 제공되는 인터페이스 에서 computeIfAbsent 메소드를 사용할 수 있습니다 .Map

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공간 소비를 높이기 위해 보다 훨씬 높습니다 .


왜 hashHashmap과 AtomicLong을 사용해야합니까?
ealeon 2019

7

int의 128보다 크거나 같은 모든 boxing이 객체 할당을 유발하기 때문에 메모리 회전이 문제가 될 수 있습니다 (Integer.valueOf (int) 참조). 가비지 수집기는 수명이 짧은 개체를 매우 효율적으로 처리하지만 성능은 어느 정도 저하됩니다.

증가 횟수가 키 수 (=이 경우 단어 수)를 크게 초과한다는 것을 알고 있다면, 대신 int holder를 사용하십시오. 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 구현을 찾으십시오. jrudolph는 GNU Trove를 언급했습니다 .

그건 그렇고,이 주제에 대한 좋은 검색어는 "히스토그램"입니다.


5

containsKey ()를 호출하는 대신 map.get을 호출하고 반환 된 값이 null인지 아닌지를 확인하는 것이 더 빠릅니다.

    Integer count = map.get(word);
    if(count == null){
        count = 0;
    }
    map.put(word, count + 1);

3

이것이 병목임을 확신합니까? 성능 분석을 수행 했습니까?

NetBeans 프로파일 러 (무료 및 NB 6.1에 내장)를 사용하여 핫스팟을보십시오.

마지막으로 JVM 업그레이드 (예 : 1.5-> 1.6)는 종종 저렴한 성능 향상 도구입니다. 빌드 번호를 업그레이드하더라도 성능이 향상 될 수 있습니다. Windows에서 실행 중이고 이것이 서버 클래스 응용 프로그램 인 경우 명령 행에서 -server를 사용하여 Server Hotspot JVM을 사용하십시오. Linux 및 Solaris 시스템에서는 자동 감지됩니다.


3

몇 가지 접근 방식이 있습니다.

  1. Google 컬렉션에 포함 된 세트와 같은 백 alorithm을 사용하십시오.

  2. 맵에서 사용할 수있는 가변 컨테이너를 작성하십시오.


    class My{
        String word;
        int count;
    }

그리고 put ( "word", new My ( "Word")); 그런 다음 존재하는지 확인하고 추가 할 때 증가시킬 수 있습니다.

innerloop 검색 및 정렬을 수행하면 성능이 저하되므로 목록을 사용하여 자체 솔루션을 롤링하지 마십시오. 첫 번째 HashMap 솔루션은 실제로 매우 빠르지 만 Google 컬렉션에있는 것과 비슷한 것이 더 좋습니다.

Google 컬렉션을 사용하여 단어를 세면 다음과 같습니다.



    HashMultiset s = new HashMultiset();
    s.add("word");
    s.add("word");
    System.out.println(""+s.count("word") );


백 알고리즘은 단어를 계산할 때 필요한 것이므로 HashMultiset을 사용하는 것은 상당히 일리가 없습니다.


3

귀하의 솔루션이 표준 방법이라고 생각하지만 직접 언급했듯이 아마도 가장 빠른 방법은 아닙니다.

GNU Trove를 볼 수 있습니다 . 그것은 모든 종류의 빠른 기본 컬렉션을 포함하는 라이브러리입니다. 귀하의 예제 는 원하는 것을 정확하게 수행하는 adjustOrPutValue 메소드가 있는 TObjectIntHashMap 을 사용합니다 .


TObjectIntHashMap에 대한 링크가 끊어졌습니다. 이것은 올바른 링크입니다 : trove4j.sourceforge.net/javadocs/gnu/trove/map/…
Erel Segal-Halevi

고마워, Erel, 나는 링크를 수정했다.
jrudolph

3

약간의 해킹이 발생할 경우 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);

3

Google Collections HashMultiset :
-사용하기 매우 우아
하지만 CPU 및 메모리 소비

가장 좋은 방법은 다음과 같습니다. Entry<K,V> getOrPut(K); (우아하고 저렴한 비용)

이러한 메소드는 해시와 색인을 한 번만 계산 한 다음 항목으로 원하는 작업을 수행 할 수 있습니다 (값 바꾸기 또는 업데이트).

더 우아한 :
- 필요한 경우 새 항목 HashSet<Entry>
get(K)넣을 수 있도록 확장하십시오
-항목은 자신의 객체가 될 수 있습니다.
->(new MyHashSet()).get(k).increment();


3

아주 간단합니다. 내장 함수 Map.java를 다음 과 같이 사용하십시오.

map.put(key, map.getOrDefault(key, 0) + 1);

이것은 값을 증가시키지 않고 현재 값을 설정하거나 키에 값이 할당되지 않은 경우 0을 설정합니다.
siegi 2015 년

당신은 값을 증가시킬 수 있습니다 ++... 세상에, 너무 간단합니다. @siegi
sudoz

레코드의 경우 : ++피연산자로 변수가 필요하지만 값만 있기 때문에이 표현식의 어느 곳에서도 작동하지 않습니다. + 1그래도 작품 추가 . 이제 귀하의 솔루션은 off99555s answer 과 동일 합니다.
siegi

2

"put"에는 "get"이 필요합니다 (중복 키가 없도록).
따라서 "put"을 직접 수행하고
이전 값이 있으면 추가를 수행하십시오.

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

1

예를 들어, 다양한 프리미티브 래퍼 Integer는 변경할 수 없으므로 AtomicLong 과 같은 방법으로 할 수 없다면 요청하는 것을 수행하는 더 간결한 방법은 없습니다 . 나는 그 순간을 갈 수 있고 업데이트 할 수 있습니다. BTW, Hashtable Collections Framework 의 일부입니다 .


1

Apache Collections Lazy Map (값을 0으로 초기화)을 사용하고 Apache Lang의 MutableIntegers를 해당 맵의 값으로 사용합니다.

가장 큰 비용은 분석법에서 맵을 두 번 처리해야합니다. 내 경우에는 한 번만하면됩니다. 그냥 값을 얻고 (없는 경우 초기화됩니다) 값을 늘리십시오.


1

기능 자바 라이브러리의 TreeMap자료 구조는이 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"를 인쇄합니다.


1

@ Vilmantas Baranauskas :이 답변과 관련하여 담당자가 있다면 의견을 말하지만 그렇지 않습니다. value ()를 동기화하지 않고 inc ()를 동기화하는 것만으로는 충분하지 않기 때문에 스레드 안전이 정의되지 않은 Counter 클래스에 주목하고 싶습니다. value ()를 호출하는 다른 스레드는 업데이트와 관계가 설정되어 있지 않으면 값을 볼 수 없습니다.


다른 사람의 답변을 참조하려면 상단에 @ [Username]을 사용하십시오 (예 : @Vilmantas Baranauskas <Content goes here>
Hank Gay

정리하기 위해 수정했습니다.
Alex Miller

1

나는 그것이 얼마나 효율적인지 모르겠지만 아래 코드도 잘 작동합니다 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

1

Eclipse Collections를 사용하는 경우을 사용할 수 있습니다 HashBag. 메모리 사용 측면에서 가장 효율적인 접근 방식이며 실행 속도 측면에서도 잘 수행됩니다.

HashBag는 객체 MutableObjectIntMap대신 기본 정수를 저장 하는로 뒷받침됩니다 Counter. 이것은 메모리 오버 헤드를 줄이고 실행 속도를 향상시킵니다.

HashBag 필요한 API를 제공하므로 Collection항목의 발생 횟수를 쿼리 할 수 .

다음은 Eclipse Collections Kata 의 예제입니다 .

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 Collections의 커미터입니다.


1

Java 8 Map :: compute ()를 사용하는 것이 좋습니다. 키가 존재하지 않는 경우도 고려합니다.

Map.compute(num, (k, v) -> (v == null) ? 1 : v + 1);

mymap.merge(key, 1, Integer::sum)?
Det

-2

많은 사람들이 Groovy 답변에 대한 Java 주제를 검색하므로 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}

-2

Java 8에서 간단하고 쉬운 방법은 다음과 같습니다.

final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
    map.computeIfAbsent("foo", key -> new AtomicLong(0)).incrementAndGet();

-3

나는 당신의 질문을 올바르게 이해하고 있기를 바랍니다. 나는 파이썬에서 Java로 왔습니다. 그래서 나는 당신의 투쟁에 공감할 수 있습니다.

당신이 가지고 있다면

map.put(key, 1)

당신은 할 것

map.put(key, map.get(key) + 1)

도움이 되었기를 바랍니다!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.