जावा में एक मानचित्र मान बढ़ाने के लिए सबसे कुशल तरीका


377

मुझे उम्मीद है कि इस मंच के लिए यह प्रश्न बहुत बुनियादी नहीं माना जाता है, लेकिन हम देखेंगे। मैं सोच रहा हूं कि बेहतर प्रदर्शन के लिए कुछ कोड को कैसे रिफलेक्टर किया जाए जो कई बार चल रहा है।

मान लें कि मैं एक मैप (शायद हैशपॉप) का उपयोग करके एक शब्द आवृत्ति सूची बना रहा हूं, जहां प्रत्येक कुंजी उस शब्द के साथ एक स्ट्रिंग है जिसे गिना जा रहा है और मान एक इंटगर है जो शब्द के टोकन मिलने पर हर बार इंक्रीमेंट होता है।

पर्ल में, ऐसे मूल्य को बढ़ाना बहुत ही आसान होगा:

$map{$word}++;

लेकिन जावा में, यह बहुत अधिक जटिल है। यहाँ वर्तमान में मैं इसे कर रहा हूँ:

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

कौन सा कोर्स नए जावा संस्करणों में ऑटोबॉक्सिंग सुविधा पर निर्भर करता है। मुझे आश्चर्य है कि अगर आप इस तरह के मूल्य में वृद्धि का एक अधिक कुशल तरीका सुझा सकते हैं। क्या कलेक्शन की रूपरेखा को आगे बढ़ाने और इसके बजाय कुछ और का उपयोग करने के लिए अच्छे प्रदर्शन के कारण हैं?

अद्यतन: मैंने कई उत्तरों का परीक्षण किया है। निचे देखो।


मुझे लगता है कि यह java.util.Hashtable के लिए समान होगा।
जर्दोलफ

2
बेशक अगर ऐसा ही होगा, क्योंकि हैशटेबल एक नक्शा है।
व्हिस्की सियार

जावा 8: computeIfAbsent उदाहरण: stackoverflow.com/a/37439971/12/1216775
akhil_mittal

जवाबों:


366

कुछ परीक्षण के परिणाम

मैंने इस सवाल का बहुत अच्छा जवाब दिया है - धन्यवाद दोस्तों - इसलिए मैंने कुछ परीक्षण चलाने का फैसला किया और पता लगाया कि वास्तव में कौन सी विधि सबसे तेज़ है। मैंने जिन पांच तरीकों का परीक्षण किया वे ये हैं:

  • प्रश्न में प्रस्तुत "ContainsKey" विधि
  • अलेक्जेंडर दिमित्रोव द्वारा सुझाए गए "टेस्टफ़ोरनुल" विधि
  • हांक गे द्वारा सुझाई गई "एटॉमिकलॉन्ग" विधि
  • "ट्रोव" विधि जोर्डोफ द्वारा सुझाई गई है
  • phax.myopenid.com द्वारा सुझाई गई "MutableInt" विधि

तरीका

यहाँ मैंने क्या किया है ...

  1. नीचे दिखाए गए मतभेदों को छोड़कर पांच कक्षाएं बनाई गईं जो समान थीं। प्रत्येक वर्ग को मेरे द्वारा प्रस्तुत परिदृश्य का एक ऑपरेशन विशिष्ट प्रदर्शन करना था: एक 10 एमबी फ़ाइल खोलना और इसे पढ़ना, फिर फ़ाइल में सभी शब्द टोकन की एक आवृत्ति गणना करना। चूंकि इसने केवल 3 सेकंड का औसत लिया, इसलिए मैंने 10 बार आवृत्ति गणना (I / O नहीं) की थी।
  2. 10 पुनरावृत्तियों के लूप को समयबद्ध किया, लेकिन I / O ऑपरेशन को नहीं किया और जावा कुकबुक में इयान डार्विन की विधि का उपयोग करके अनिवार्य रूप से लिया गया कुल समय (घड़ी सेकंड में) दर्ज किया ।
  3. श्रृंखला में सभी पांच परीक्षण किए, और फिर तीन बार ऐसा किया।
  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% से अधिक का प्रदर्शन बढ़ा देते हैं। हालांकि, अगर थ्रेडिंग एक मुद्दा है, तो एटॉमिकलॉन्ग दूसरों की तुलना में अधिक आकर्षक हो सकता है (मुझे वास्तव में यकीन नहीं है)। मैंने TestForNull के साथ भी दौड़ लगाईfinal चर के , लेकिन अंतर नगण्य था।

ध्यान दें कि मैंने अलग-अलग परिदृश्यों में मेमोरी के उपयोग को कम नहीं किया है। मुझे किसी को भी यह सुनकर खुशी होगी कि किस प्रकार म्यूचुअलेबल और ट्रोव के तरीकों में अच्छी अंतर्दृष्टि है, जो स्मृति उपयोग को प्रभावित करने की संभावना है।

व्यक्तिगत रूप से, मुझे 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);
}

AtomicLong

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
बढ़िया काम, अच्छा किया। एक मामूली टिप्पणी - एटॉमिकलॉन्ग कोड में putIfAbsent () कॉल एक नए एटॉमिकलॉन्ग (0) को हटा देगा, भले ही वह पहले से ही नक्शे में हो। यदि आप इसके बजाय यदि (map.get (key) == null) का उपयोग करने के लिए इसे घुमाते हैं, तो आपको संभवतः उन परीक्षा परिणामों में सुधार मिलेगा।
लेह कैल्डवेल

2
मैंने हाल ही में MutableInt के समान दृष्टिकोण के साथ एक ही काम किया। मुझे यह सुनकर खुशी हुई कि यह इष्टतम समाधान था (मैंने बिना किसी परीक्षण के, यह मान लिया था)।
किप

यह सुनकर अच्छा लगा कि आप मुझसे ज्यादा तेज़ हैं, किप। ;-) मुझे पता है अगर आप उस दृष्टिकोण के लिए कोई कमियां पता चलता है।
ग्रेगरी

4
एटॉमिक लॉन्ग केस में इसे एक चरण में करना अधिक कुशल नहीं होगा (इसलिए आपके पास 2 के बजाय केवल 1 महंगा ऑपरेशन है) "map.putIfAbsent (शब्द, नया एटॉमिकलॉन्ग (0))। incrementAfGet ();"
smartnut007

1
@ वृत्तिका आपने जावा 8 पर विचार किया freq.compute(word, (key, count) -> count == null ? 1 : count + 1)? आंतरिक रूप से यह एक से कम हैशेड लुकअप करता है containsKey, यह देखना दिलचस्प होगा कि यह लैंबडा के कारण दूसरों की तुलना कैसे करता है।
ट्वीस्ट्रीम

255

अब जावा 8 का उपयोग करने का एक छोटा तरीका है Map::merge

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

यह क्या करता है:

  • यदि कुंजी मौजूद नहीं है, तो मान के रूप में 1 डालें
  • अन्यथा कुंजी से जुड़े मूल्य का योग 1

अधिक जानकारी यहाँ


हमेशा प्यार करता हूँ जावा 8. क्या यह परमाणु है? या क्या मुझे इसे एक सिंक्रनाइज़ के साथ घेरना चाहिए?
टिआना

4
यह मेरे लिए काम करने के लिए प्रतीत नहीं हुआ लेकिन map.merge(key, 1, (a, b) -> a + b); किया
russter

2
@ टीना एटमॉसिटी विशेषताओं को लागू करना विशिष्ट है, सीएफ। डॉक्स : "डिफ़ॉल्ट कार्यान्वयन इस पद्धति के सिंक्रोनाइज़ेशन या एटोमिसिटी गुणों के बारे में कोई गारंटी नहीं देता है। एटॉमिसिटी गारंटी प्रदान करने वाला कोई भी कार्यान्वयन इस पद्धति को ओवरराइड करना चाहिए और इसकी संगणकीय संपत्तियों को दस्तावेज़ित करना चाहिए। विशेष रूप से, सबइंटरनेशन कॉनकंट्राप्पल के सभी कार्यान्वयनों को फ़ंक्शन एक बार लागू किया जाना चाहिए या नहीं। यदि मूल्य मौजूद नहीं है तो केवल परमाणु।
jensgram

2
ग्रूवी के लिए, यह Integer::sumबायफ़ंक्शन के रूप में स्वीकार नहीं करेगा , और @russter को उस तरह से जवाब देना पसंद नहीं था जिस तरह से लिखा गया था। इसने मेरे लिए काम कियाMap.merge(key, 1, { a, b -> a + b})
23: ’

2
@russter, मुझे पता है कि आपकी टिप्पणी एक साल पहले खत्म हो गई थी लेकिन क्या आपको याद है कि यह आपके लिए काम क्यों नहीं हुआ? क्या आपको संकलन त्रुटि मिली या मूल्य वृद्धि नहीं हुई थी?
पॉल

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
धन्यवाद, यह वास्तव में मददगार था। यह बेंचमार्क में अमरूद की मल्टीसेट (उदाहरण के लिए, हाशमुलिसेट) को जोड़ने के लिए अच्छा होगा।
कबाद

34

Google अमरूद आपका मित्र है ...

... कम से कम कुछ मामलों में। उनके पास यह अच्छा एटॉमिकलॉन्ग स्पा है । विशेष रूप से अच्छा है क्योंकि आप लंबे समय से काम कर रहे हैं से अपने नक्शे में मूल्य के ।

उदाहरण के लिए

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

इसके अलावा मूल्य में 1 और जोड़ना संभव है:

map.getAndAdd(word, 112L); 

7
AtomicLongMap#getAndAddएक आदिम लेता है longऔर आवरण वर्ग नहीं; करने का कोई मतलब नहीं है new Long()। और AtomicLongMapएक पैरामीटर प्रकार है; आपको इसे घोषित करना चाहिए था AtomicLongMap<String>
हेल्डर परेरा

32

@ हंक गे

अनुवर्ती के रूप में मेरी खुद की (बल्कि बेकार) टिप्पणी: ट्रोव को जाने का रास्ता दिखता है। यदि, किसी भी कारण से, आप मानक JDK के साथ रहना चाहते हैं, तो समवर्ती मैप और एटॉमिकलॉन्ग कोड को एक छोटा सा अच्छा बनाने वाला बना सकते हैं , हालांकि YMMV।

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

के 1लिए मानचित्र में मान के रूप में छोड़ देंगे foo। वास्तविक रूप से, थ्रेडिंग के लिए मित्रता बढ़ गई है यह सब इस दृष्टिकोण की सिफारिश करना है।


9
PutIfAbsent () मान लौटाता है। किसी स्थानीय चर में दिए गए मान को संग्रहीत करना और उसे फिर से कॉल करने के बजाय incrementAndGet () में उपयोग करना एक बड़ा सुधार हो सकता है।
smartnut007

putIfAbsent एक अशक्त मान वापस कर सकता है यदि निर्दिष्ट कुंजी पहले से ही मानचित्र के अंदर एक मूल्य से जुड़ी नहीं है तो मैं लौटे मूल्य का उपयोग करने के लिए सावधान रहूंगा। 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);

और यह है कि आप साधारण कोड के साथ एक मूल्य कैसे बढ़ाते हैं।

फायदा:

  • नए वर्ग को जोड़ने या उत्परिवर्तित इंट की दूसरी अवधारणा का उपयोग करने की आवश्यकता नहीं है
  • किसी पुस्तकालय पर निर्भर नहीं
  • समझने में आसान है कि वास्तव में क्या हो रहा है (बहुत अधिक अमूर्त नहीं)

नकारात्मक पहलू:

  • हैश मैप को दो बार () और पुट () के लिए खोजा जाएगा। तो यह सबसे अधिक प्रदर्शन करने वाला कोड नहीं होगा।

सैद्धांतिक रूप से, एक बार जब आप कॉल करते हैं (), तो आप पहले से ही जानते हैं कि कहां रखा जाए (), इसलिए आपको फिर से खोज नहीं करनी चाहिए। लेकिन हैश मैप में खोज करने में आमतौर पर बहुत कम समय लगता है जिससे आप इस प्रदर्शन के मुद्दे को अनदेखा कर सकते हैं।

लेकिन अगर आप इस मुद्दे के बारे में बहुत गंभीर हैं, तो आप एक पूर्णतावादी हैं, मर्ज विधि का उपयोग करने का एक और तरीका है, यह (शायद) पिछले कोड स्निपेट की तुलना में अधिक कुशल है जैसा कि आप (सैद्धांतिक रूप से) केवल एक बार मानचित्र की खोज करेंगे: (हालांकि यह कोड पहली नजर से स्पष्ट नहीं है, यह छोटा और प्रदर्शन करने वाला है)

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

सुझाव: आपको अधिकांश समय में प्रदर्शन के कम लाभ से अधिक कोड की पठनीयता की परवाह करनी चाहिए। यदि पहला कोड स्निपेट आपके लिए समझना आसान है तो इसका उपयोग करें। लेकिन अगर आप 2 वें को ठीक समझने में सक्षम हैं तो आप इसके लिए भी जा सकते हैं!


GetOfDefault Method JAVA 7 में उपलब्ध नहीं है। मैं इसे JAVA 7 में कैसे प्राप्त कर सकता हूं?
तन्वी

1
आपको अन्य उत्तरों पर निर्भर रहना पड़ सकता है। यह केवल जावा 8. में काम करता है
15:99 पर ऑफ 10555

1
मर्ज समाधान के लिए +1, यह उच्चतम प्रदर्शन करने वाला कार्य होगा क्योंकि आपको केवल हैशकोड गणना के लिए 1 बार भुगतान करना होगा (यदि आप जिस मानचित्र का सही तरीके से उपयोग कर रहे हैं उस स्थिति में), इसके लिए संभावित भुगतान के बजाय 3 बार
फेरीबग

2
विधि inference का उपयोग: map.merge (कुंजी, 1, पूर्णांक :: योग)
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>, इसलिए आप बॉक्सिंग लागतों को नहीं लेंगे।


उपरोक्त answeer को इनवॉयर प्रतिक्रिया को प्रतिबिंबित करने की आवश्यकता है। Api अपनी पोस्टिंग के बाद से बदल गया है (3 साल पहले :))
स्टीव

क्या count()मल्टीसेट पर विधि O (1) या O (n) समय (सबसे खराब) में चलती है? डॉक्स इस बिंदु पर अस्पष्ट हैं।
एडम पार्किन

इस तरह की चीज़ के लिए मेरा एल्गोरिथ्म: अगर (hasApacheLib (बात)) apacheLib लौटें; और अगर (hasOnGuava (बात)) अमरूद लौटाते हैं। आमतौर पर मैं इन दो चरणों से पीछे नहीं हटता। :)
पचो_मब

22

आपको इस तथ्य के बारे में पता होना चाहिए कि आपका मूल प्रयास

int count = map.containsKey (शब्द)? map.get (शब्द): 0;

एक मानचित्र पर दो संभावित रूप से महंगे ऑपरेशन शामिल हैं, अर्थात् containsKeyऔर get। पूर्व में बाद के समान एक ऑपरेशन संभवत: बहुत अच्छा होता है, इसलिए आप एक ही काम दो बार कर रहे हैं !

यदि आप मानचित्र के लिए एपीआई को देखते हैं, तो getआमतौर पर परिचालन nullतब लौटता है जब मानचित्र में अनुरोधित तत्व नहीं होता है।

ध्यान दें कि यह एक समाधान जैसा बना देगा

map.put (कुंजी, map.get (कुंजी) + 1);

खतरनाक है, क्योंकि यह उपज हो सकता है NullPointerException। आपको nullपहले जांच करनी चाहिए ।

यह भी ध्यान रखें , और यह बहुत महत्वपूर्ण है, कि HashMapरों सकते हैं शामिल nullsपरिभाषा के द्वारा। तो हर लौटा नहीं nullकहता है "ऐसा कोई तत्व नहीं है"। इस संबंध में, वास्तव में आपको यह बताने से अलगcontainsKey व्यवहार करता है कि क्याget ऐसा कोई तत्व है। जानकारी के लिए एपीआई देखें।

आपके मामले के लिए, हालाँकि, आप एक संग्रहीत nullऔर "NoSuchElement" के बीच अंतर नहीं करना चाह सकते हैं । यदि आप अनुमति नहीं देना चाहते हैं तो nullआप पसंद कर सकते हैंHashtable । अन्य उत्तरों में पहले से ही प्रस्तावित एक रैपर लाइब्रेरी का उपयोग करना आपके आवेदन की जटिलता के आधार पर, मैनुअल उपचार के लिए एक बेहतर समाधान हो सकता है।

उत्तर को पूरा करने के लिए (और मैं सबसे पहले इसे संपादित करने के लिए धन्यवाद भूल गया!), इसे मूल रूप से करने का सबसे अच्छा तरीका है, getएक finalचर में, इसकी जांच करना nullऔर putइसमें वापस आना 1। चर होना चाहिए finalक्योंकि यह वैसे भी अपरिवर्तनीय है। संकलक को इस संकेत की आवश्यकता नहीं हो सकती है, लेकिन इसका रास्ता स्पष्ट है।

अंतिम हशपप मानचित्र = उत्पन्नरंजनमहशमप ();
अंतिम वस्तु कुंजी = fetchSomeKey ();
अंतिम पूर्णांक i = map.get (कुंजी);
अगर (i! null) {
    map.put (i + 1);
} अन्य {
    // कुछ करो
}

यदि आप ऑटोबॉक्सिंग पर भरोसा नहीं करना चाहते हैं, तो आपको map.put(new Integer(1 + i.getValue()));इसके बजाय कुछ कहना चाहिए ।


ग्रूवी में प्रारंभिक अनमैप्ड / अशक्त मानों की समस्या से बचने के लिए मैं कर रहा हूं: counts.put (key, (counts.get (key) ?: 0) + 1) // overly जटिल संस्करण ++
Joe Atzberber

2
या, सबसे सरल: मायने रखता है = [:]। WithDefault {0} // ++ दूर
जो एत्ज़बर्गर

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.valueOf के साथ) इतना नहीं होना चाहिए।


5
पहली बार जब आप इसे मानचित्र में डालते हैं, तो आप MutableInt को बंद नहीं करना चाहते हैं?
टॉम हैटिन -

5
अपाचे के कॉमन्स-लैंग में पहले से ही आपके लिए लिखा गया एक म्यूटेबल है।
सिंगलशॉट

11

आप जावा 8 में दिए गए इंटरफ़ेस में कम्प्यूट्यूट विधि का उपयोग कर सकते हैं ।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जाँच करता है कि निर्दिष्ट कुंजी पहले से ही एक मूल्य के साथ जुड़ा हुआ है या नहीं? यदि कोई संबद्ध मूल्य नहीं है, तो यह दिए गए मैपिंग फ़ंक्शन का उपयोग करके इसके मूल्य की गणना करने का प्रयास करता है। किसी भी मामले में यह वर्तमान (मौजूदा या कंप्यूटेड) मान निर्दिष्ट कुंजी के साथ जुड़ा हुआ है, या यदि गणना किया गया मान शून्य है, तो शून्य देता है।

साइड नोट पर यदि आपके पास एक ऐसी स्थिति है जहां कई थ्रेड्स एक सामान्य राशि को अपडेट करते हैं तो आप लॉन्गएडर क्लास पर एक नज़र डाल सकते हैं। और उच्च विवाद, इस वर्ग के थ्रूपुट की अपेक्षा AtomicLongउच्च स्थान की खपत की तुलना में काफी अधिक है ।


समवर्ती हशमैप और एटॉमिकलॉन्ग क्यों?
ईगल

7

मेमोरी रोटेशन यहां एक मुद्दा हो सकता है, क्योंकि इंट के हर बॉक्सिंग से बड़ा या 128 के बराबर वस्तु आवंटन का कारण बनता है (देखें Integer.valueOf (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();
}

यदि आपको अत्यधिक प्रदर्शन की आवश्यकता है, तो मानचित्र कार्यान्वयन के लिए देखें जो सीधे आदिम मूल्य प्रकारों के अनुरूप है। jrudolph ने GNU Trove का उल्लेख किया ।

वैसे, इस विषय का एक अच्छा खोज शब्द "हिस्टोग्राम" है।


5

कॉल करने के बजाए इसमें शामिल हैकेय () यह केवल मैप कॉल करने के लिए तेज़ है। चेक करें और जांचें कि लौटा मूल्य शून्य है या नहीं।

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

3

क्या आप सुनिश्चित हैं कि यह एक अड़चन है? क्या आपने कोई प्रदर्शन विश्लेषण किया है?

हॉटस्पॉट्स देखने के लिए NetBeans profiler (इसके मुफ़्त और एनबी 6.1 में निर्मित) का उपयोग करने का प्रयास करें।

अंत में, एक जेवीएम अपग्रेड (1.5-> 1.6 से कहते हैं) अक्सर एक सस्ता प्रदर्शन बूस्टर है। यहां तक ​​कि बिल्ड नंबर में अपग्रेड भी अच्छा प्रदर्शन बढ़ा सकता है। यदि आप विंडोज पर चल रहे हैं और यह सर्वर क्लास एप्लिकेशन है, तो सर्वर हॉटस्पॉट JVM का उपयोग करने के लिए कमांड लाइन पर -server का उपयोग करें। लिनक्स और सोलारिस मशीनों पर यह ऑटोडेट किया गया है।


3

दृष्टिकोण के एक जोड़े हैं:

  1. Google संग्रह में शामिल सेट की तरह एक थैला अलॉरिथम का उपयोग करें।

  2. म्यूटेबल कंटेनर बनाएँ जिसका उपयोग आप मानचित्र में कर सकते हैं:


    class My{
        String word;
        int count;
    }

और पुट ("शब्द", नया मेरा ("शब्द")) का उपयोग करें; फिर आप जाँच कर सकते हैं कि क्या मौजूद है और जोड़ते समय वृद्धि हुई है।

सूचियों का उपयोग करके अपने स्वयं के समाधान को रोल करने से बचें, क्योंकि यदि आप इनरलूप खोज और छंटाई करते हैं, तो आपका प्रदर्शन बदबू देगा। पहला HashMap समाधान वास्तव में काफी तेज़ है, लेकिन Google संग्रह में पाया गया एक उचित तरीका शायद बेहतर है।

Google संग्रह का उपयोग करके शब्दों की गिनती, कुछ इस तरह दिखता है:



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


HashMultiset का उपयोग करना काफी सुरुचिपूर्ण है, क्योंकि एक बैग-एल्गोरिथ्म सिर्फ वही है जो आपको शब्दों की गिनती करते समय चाहिए।


3

मुझे लगता है कि आपका समाधान मानक तरीका होगा, लेकिन - जैसा कि आपने खुद को नोट किया है - यह संभवतः सबसे तेज़ तरीका संभव नहीं है।

आप GNU ट्रोव को देख सकते हैं । यह एक पुस्तकालय है जिसमें सभी प्रकार के तेज आदिम संग्रह शामिल हैं। आपका उदाहरण एक TOBjectIntHashMap का उपयोग करेगा जिसमें एक विधि समायोजन हैOrPutValue जो वास्तव में आप क्या चाहते हैं।


TObjectIntHashMap का लिंक टूट गया है। यह सही लिंक है: trove4j.sourceforge.net/javadocs/gnu/trove/map/…
Erel Segal-

धन्यवाद, Erel, मैंने लिंक तय किया।
वृद्धावस्था

3

MutableInt दृष्टिकोण पर एक भिन्नता जो कि और भी तेज़ हो सकती है, यदि कोई हैक के लिए, एक एकल-तत्व का उपयोग करने के लिए है int </a>

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

यह दिलचस्प होगा यदि आप इस भिन्नता के साथ अपने प्रदर्शन परीक्षणों को फिर से चला सकते हैं। यह सबसे तेज हो सकता है।


संपादित करें: उपरोक्त पैटर्न ने मेरे लिए ठीक काम किया, लेकिन अंततः मैं ट्रोव के संग्रह का उपयोग करके अपने द्वारा बनाए गए कुछ बहुत बड़े मानचित्रों में मेमोरी का आकार कम करने के लिए बदल गया - और एक बोनस के रूप में यह भी तेज था।

एक बहुत अच्छी विशेषता यह है कि TObjectIntHashMapवर्ग में एक एकल adjustOrPutValueकॉल है, जो इस बात पर निर्भर करता है कि उस कुंजी पर पहले से ही एक मूल्य है, या तो एक प्रारंभिक मूल्य डाल देगा या मौजूदा मूल्य में वृद्धि करेगा। यह वेतन वृद्धि के लिए एकदम सही है:

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>();
...
map.adjustOrPutValue(key, 1, 1);

3

Google संग्रह 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 सेट करता है यदि कोई मान कुंजी को नहीं सौंपा गया था।
21 मई

आप मान बढ़ा सकते हैं ++... OMG, यह बहुत आसान है। @ सिगी
सूडोज

रिकॉर्ड के लिए: ++इस अभिव्यक्ति में कहीं भी काम नहीं करता है क्योंकि एक चर की आवश्यकता इसके ऑपरेंड के रूप में होती है लेकिन सिर्फ मूल्य हैं। आपके + 1कार्यों का जोड़ हालांकि। अब आपका समाधान वही है जो ऑफ़99555 के उत्तर में है
सीगी

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

1

विभिन्न आदिम आवरण, उदाहरण के लिए, Integerअपरिवर्तनीय हैं , इसलिए वास्तव में एक अधिक संक्षिप्त तरीका नहीं है कि आप क्या पूछ रहे हैं जब तक कि आप इसे एटॉमिकलॉन्ग जैसी चीज के साथ नहीं कर सकते । मैं एक मिनट में जा सकता हूं और अपडेट कर सकता हूं। BTW, हैशटेबल कलेक्शंस फ्रेमवर्क का एक हिस्सा है ।


1

मैं अपाचे कलेक्शंस लेज़ी मैप (0 पर मानों को इनिशियलाइज़ करने के लिए) का उपयोग करूँगा और अपाचे लैंग से 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: इस उत्तर के बारे में, मैं टिप्पणी करता हूं कि क्या मेरे पास प्रतिनिधि बिंदु हैं, लेकिन मैं नहीं। मैं यह नोट करना चाहता था कि वहाँ परिभाषित काउंटर क्लास थ्रेड-सुरक्षित नहीं है क्योंकि यह केवल सिंक () मूल्य को सिंक्रनाइज़ किए बिना पर्याप्त नहीं है। वैल्यू को देखने के लिए अन्य थ्रेड्स कॉलिंग वैल्यू () की वैल्यू देखने की गारंटी नहीं दी जाती है, जब तक कि अपडेट से पहले संबंध स्थापित न हो जाए।


यदि आप किसी के उत्तर को संदर्भित करना चाहते हैं, तो शीर्ष पर @ [उपयोगकर्ता नाम] का उपयोग करें, जैसे @Vilmantas बरनौस्का <सामग्री यहाँ जाती है>
हांक गे

मैंने इसे साफ करने के लिए वह संशोधन किया।
एलेक्स मिलर

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

यदि आप ग्रहण संग्रह का उपयोग कर रहे हैं , तो आप एक का उपयोग कर सकते हैं HashBag। यह मेमोरी उपयोग के मामले में सबसे कुशल दृष्टिकोण होगा और यह निष्पादन की गति के मामले में भी अच्छा प्रदर्शन करेगा।

HashBagएक के द्वारा समर्थित है MutableObjectIntMapजो के बजाय आदिम ints संग्रहीत करता है Counterवस्तुओं। यह मेमोरी ओवरहेड को कम करता है और निष्पादन की गति में सुधार करता है।

HashBagएपीआई की आवश्यकता होती है क्योंकि यह 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"));

नोट: मैं एक्लिप्स कलेक्शन के लिए कमिटेटर हूं।


1

मैं जावा 8 मैप :: कंप्यूट () का उपयोग करने का सुझाव देता हूं। यह उस मामले पर विचार करता है जब कोई कुंजी मौजूद नहीं है, भी।

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

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

-2

चूँकि बहुत से लोग ग्रूवी उत्तरों के लिए जावा विषय खोजते हैं, यहाँ पर आप इसे ग्रूवी में कैसे कर सकते हैं:

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

जावा 8 में सरल और आसान तरीका निम्नलिखित है:

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

-3

आशा है कि मैं आपके प्रश्न को सही ढंग से समझ रहा हूं, मैं पायथन से जावा में आ रहा हूं ताकि मैं आपके संघर्ष के साथ सहानुभूति रख सकूं।

यदि आपके पास है

map.put(key, 1)

तुम करोगे

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

उम्मीद है की यह मदद करेगा!

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.