[Java] जावा में मानचित्र मान बढ़ाने के लिए सबसे प्रभावी तरीका


Answers

ठीक है, एक पुराना सवाल हो सकता है, लेकिन जावा 8 के साथ एक छोटा रास्ता है:

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

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

Question

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

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

पर्ल में, इस तरह के मूल्य में वृद्धि करना मुश्किल से आसान होगा:

$map{$word}++;

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

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

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

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




Google संग्रह HashMultiset:
उपयोग करने के लिए काफी सुरुचिपूर्ण
- लेकिन सीपीयू और मेमोरी का उपभोग करें

सबसे अच्छा तरीका होगा जैसे: Entry<K,V> getOrPut(K); (सुरुचिपूर्ण, और कम लागत)

इस तरह की एक विधि केवल एक बार हैश और इंडेक्स की गणना करेगी, और फिर हम जो भी हम चाहते हैं वह कर सकते हैं (या तो मूल्य को प्रतिस्थापित या अपडेट करें)।

और अधिक सुंदर:
- एक HashSet<Entry> ले लो
- इसे विस्तारित करें ताकि यदि आवश्यक हो तो get(K) एक नई प्रविष्टि डाल दें
- प्रवेश आपकी खुद की वस्तु हो सकती है।
-> (new MyHashSet()).get(k).increment();




विभिन्न आदिम रैपर, उदाहरण के लिए, Integer अपरिवर्तनीय हैं, इसलिए आप जो भी पूछ रहे हैं, उसे करने के लिए वास्तव में एक और संक्षिप्त तरीका नहीं है जब तक कि आप इसे AtomicLong जैसे कुछ नहीं कर सकते। मैं इसे एक मिनट और अपडेट में जा सकता हूं। बीटीडब्ल्यू, Hashtable संग्रह फ्रेमवर्क का हिस्सा है।




@ हैंक गे

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

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

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




एक और तरीका एक परिवर्तनीय पूर्णांक बना रहा होगा:

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 के साथ भी) की तुलना में ओवरहेड इतना नहीं होना चाहिए।




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

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




मुझे नहीं पता कि यह कितना कुशल है लेकिन नीचे दिया गया कोड भी काम करता है। आपको शुरुआत में एक 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



क्या आप वाकई एक बाधा है? क्या आपने कोई प्रदर्शन विश्लेषण किया है?

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

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




कार्यात्मक जावा लाइब्रेरी के 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" प्रिंट करता है।




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

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

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

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




इस तरह की चीज़ के लिए Google संग्रह लाइब्रेरी को देखना हमेशा अच्छा विचार है। इस मामले में एक Multiset चाल करेगा:

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

कुंजी / प्रविष्टियों आदि पर पुनरावृत्ति के लिए मानचित्र जैसी विधियां हैं। आंतरिक रूप से कार्यान्वयन वर्तमान में HashMap<E, AtomicInteger> का उपयोग करता है, इसलिए आपको मुक्केबाजी लागत नहीं लगेगी।




मेमोरी रोटेशन यहां एक मुद्दा हो सकता है, क्योंकि 128 से अधिक या उसके बराबर int की प्रत्येक मुक्केबाजी ऑब्जेक्ट आवंटन का कारण बनती है (Integer.valueOf (int) देखें)। हालांकि कचरा कलेक्टर बहुत ही कुशलता से अल्पकालिक वस्तुओं से निपटता है, प्रदर्शन कुछ हद तक भुगतना होगा।

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

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