HashMap जावा 8 कार्यान्वयन


92

निम्नलिखित लिंक दस्तावेज़ के अनुसार: जावा हैशपप कार्यान्वयन

मैं HashMap(या बल्कि, एक वृद्धि HashMap) के कार्यान्वयन से उलझन में हूं । मेरे प्रश्न हैं:

पहले तो

static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;

इन स्थिरांक का उपयोग क्यों और कैसे किया जाता है? मैं इसके लिए कुछ स्पष्ट उदाहरण चाहता हूं। वे इसके साथ प्रदर्शन हासिल कैसे कर रहे हैं?

दूसरे

यदि आप HashMapJDK में स्रोत कोड देखते हैं , तो आपको निम्नलिखित स्थिर आंतरिक वर्ग मिलेगा:

static final class TreeNode<K, V> extends java.util.LinkedHashMap.Entry<K, V> {
    HashMap.TreeNode<K, V> parent;
    HashMap.TreeNode<K, V> left;
    HashMap.TreeNode<K, V> right;
    HashMap.TreeNode<K, V> prev;
    boolean red;

    TreeNode(int arg0, K arg1, V arg2, HashMap.Node<K, V> arg3) {
        super(arg0, arg1, arg2, arg3);
    }

    final HashMap.TreeNode<K, V> root() {
        HashMap.TreeNode arg0 = this;

        while (true) {
            HashMap.TreeNode arg1 = arg0.parent;
            if (arg0.parent == null) {
                return arg0;
            }

            arg0 = arg1;
        }
    }
    //...
}

इसका उपयोग कैसे किया जा सकता है? मुझे एल्गोरिथ्म का स्पष्टीकरण चाहिए

जवाबों:


224

HashMapएक निश्चित संख्या में बाल्टियाँ होती हैं। यह hashCodeनिर्धारित करने के लिए कौन सी बाल्टी का उपयोग करता है। सरलता के लिए इसे एक मापांक के रूप में कल्पना करें।

अगर हमारा हैशकोड 123456 है और हमारे पास 4 बाल्टी हैं, 123456 % 4 = 0तो आइटम पहली बाल्टी, बकेट 1 में जाता है।

हैश मैप

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

लिंक की गई बाल्टी

लेकिन आप अच्छे हैश कार्यों को लागू करने के लिए लोगों पर भरोसा नहीं कर सकते। लोग अक्सर खराब हैश कार्यों को लिखेंगे जिसके परिणामस्वरूप गैर-समान वितरण होगा। यह भी संभव है कि हम अपने इनपुट्स से अनलकी हो जाएं।

ख़राब हैशमैप

यह वितरण जितना कम है, आगे हम O (1) संचालन से आगे बढ़ रहे हैं और हम O (n) संचालन के करीब जा रहे हैं।

अगर बाल्टी बहुत बड़ी हो जाती है, तो हशमप के कार्यान्वयन को लिंक्ड सूचियों के बजाय कुछ बाल्टी को पेड़ों में व्यवस्थित करके इसे कम करने की कोशिश करता है। यह वही TREEIFY_THRESHOLD = 8है जिसके लिए है यदि एक बाल्टी में आठ से अधिक आइटम हैं, तो उसे एक पेड़ बनना चाहिए।

पेड़ की बाल्टी

यह पेड़ एक लाल-काला पेड़ है। यह पहली बार हैश कोड द्वारा सॉर्ट किया गया है। यदि हैश कोड समान हैं, तो यह उपयोग करता हैcompareTo पद्धति का है Comparableयदि ऑब्जेक्ट उस इंटरफ़ेस को लागू करते हैं, और पहचान हैश कोड।

यदि प्रविष्टियों को मानचित्र से हटा दिया जाता है, तो बाल्टी में प्रविष्टियों की संख्या ऐसी कम हो सकती है कि यह पेड़ संरचना अब आवश्यक नहीं है। यही तो हैUNTREEIFY_THRESHOLD = 6 है। यदि बाल्टी में तत्वों की संख्या छह से कम हो जाती है, तो हम लिंक की गई सूची का उपयोग करने के लिए वापस जा सकते हैं।

अंत में, वहाँ है MIN_TREEIFY_CAPACITY = 64

जब एक हैश मानचित्र आकार में बढ़ता है, तो यह स्वचालित रूप से अधिक बाल्टी होने के लिए खुद को आकार देता है। यदि हमारे पास एक छोटा हैश नक्शा है, तो हमारे पास बहुत पूर्ण बाल्टी मिलने की संभावना काफी अधिक है, क्योंकि हमारे पास सामान रखने के लिए कई अलग-अलग बाल्टी नहीं हैं। यह बड़ा हैश का नक्शा बेहतर है, कम बाल्टी वाले अधिक बाल्टी के साथ। यह स्थिरांक मूल रूप से पेड़ों में बाल्टी बनाने की शुरुआत नहीं करने के लिए कहता है यदि हमारा हैश मैप बहुत छोटा है - तो इसके बजाय पहले बड़ा होना चाहिए।


प्रदर्शन लाभ के बारे में आपके प्रश्न का उत्तर देने के लिए, इन आशाओं को सबसे खराब स्थिति में सुधार करने के लिए जोड़ा गया था । मैं केवल अटकलें लगा रहा हूं, लेकिन आप शायद इन आशाओं की वजह से ध्यान देने योग्य प्रदर्शन सुधार देखेंगे क्योंकि यदि आपका hashCodeकार्य बहुत अच्छा नहीं था।


3
एक गैर-समान वितरण हमेशा खराब हैश कार्यों का संकेत नहीं होता है। कुछ डेटा प्रकार, उदाहरण के लिए String, intहैशकोड की तुलना में एक बड़ा मूल्य स्थान है , इसलिए, टकराव अपरिहार्य हैं। अब यह वास्तविक मूल्यों पर निर्भर करता है, जैसे वास्तविक Stringएस, आप नक्शे में डालते हैं, चाहे आपको एक समान वितरण मिलता है या नहीं। एक बुरा वितरण सिर्फ बुरी किस्मत का परिणाम हो सकता है।
होल्गर

3
+1, मैं जोड़ना चाहूंगा कि एक विशिष्ट परिदृश्य जो इस पेड़ के दृष्टिकोण को कम करता है वह हैश टक्कर डीओएस हमला हैjava.lang.Stringएक नियतात्मक, गैर-क्रिप्टोग्राफिक है hashCode, इसलिए हमलावर टकराने वाले हैशकोड को अलग-अलग स्ट्रिंग्स बना सकते हैं। इस अनुकूलन से पहले, यह HashMap संचालन को O (n)-समय के लिए नीचा दिखा सकता है, अब यह उन्हें O (लॉग (n)) तक नीचा दिखाता है।
माइकफहै

1
+1, if the objects implement that interface, else the identity hash code.मैं इस दूसरे भाग को खोज रहा था।
नंबर 945

1
यदि आप इसे ओवरराइड नहीं करते हैं, तो @NateGlenn को डिफ़ॉल्ट हैश कोड
माइकल

मुझे नहीं मिला "यह लगातार मूल रूप से पेड़ों में बाल्टी बनाने शुरू नहीं करने के लिए कहता है यदि हमारा हैश नक्शा बहुत छोटा है - तो इसके बजाय पहले बड़ा होने का आकार बदलना चाहिए।" के लिए MIN_TREEIFY_CAPACITY। क्या इसका मतलब है "एक बार जब हम एक चाबी डालते हैं जिसे बाल्टी में पहले से ही 8 ( TREEIFY_THRESHOLD) कीज़ के साथ हैशेड किया जाना है और अगर वहाँ पहले से ही 64 ( MIN_TREEIFY_CAPACITY) कीज़ हैं HashMap, तो उस बाल्टी की लिंक्ड सूची को संतुलित पेड़ में बदल दिया जाता है।"
अनिर

16

इसे सरल बनाने के लिए (जितना मैं सरल कर सकता था) + कुछ और विवरण।

ये गुण बहुत सी आंतरिक चीज़ों पर निर्भर करते हैं जो सीधे समझने से पहले - समझने के लिए बहुत अच्छा होगा।

TREEIFY_THRESHOLD -> जब एक एकल बाल्टी इस तक पहुँचती है (और कुल संख्या पार हो जाती है MIN_TREEIFY_CAPACITY), तो इसे एक में बदल दिया जाता है पूरी तरह से संतुलित लाल / काले पेड़ के नोड में बदल जाती है । क्यों? क्योंकि खोज की गति। इसके बारे में एक अलग तरीके से सोचें:

यह ले जाएगा अधिक से अधिक 32 चरणों के साथ एक बाल्टी / bin में एक प्रविष्टि के लिए खोज करने के लिए Integer.MAX_VALUE प्रविष्टियों।

अगले विषय के लिए कुछ परिचय। डिब्बे / बाल्टी की संख्या हमेशा दो की शक्ति क्यों होती है ? कम से कम दो कारण: नकारात्मक संख्याओं पर मोडुलो ऑपरेशन और मोडुलो की तुलना में तेज नकारात्मक होगा। और आप एक एंट्री को "निगेटिव" बकेट में नहीं डाल सकते:

 int arrayIndex = hashCode % buckets; // will be negative

 buckets[arrayIndex] = Entry; // obviously will fail

बजाय बजाय modulo के बजाय एक अच्छी चाल का उपयोग किया जाता है:

 (n - 1) & hash // n is the number of bins, hash - is the hash function of the key

यह शब्दार्थ संचालन के समान शब्दार्थ है । यह निचले बिट्स को रखेगा। यह एक दिलचस्प परिणाम है जब आप करते हैं:

Map<String, String> map = new HashMap<>();

ऊपर के मामले में, एक प्रविष्टि कहाँ जाती है इसका निर्णय केवल पिछले 4 बिट्स के आधार पर लिया जाता है हैशकोड के ।

यह वह जगह है जहाँ बाल्टियाँ गुणा करने से खेल में आता है। कुछ शर्तों के तहत ( सटीक विवरणों में व्याख्या करने के लिए बहुत समय लगेगा ), बाल्टी आकार में दोगुनी हैं। क्यों? जब बाल्टी आकार में दोगुनी हो जाती है, तो एक और बिट खेल में आ जाती है

तो आपके पास 16 बाल्टी हैं - पिछले 4 हैशकोड के बिट्स तय करते हैं कि एक प्रविष्टि कहां जाती है। आप बकेट को दोगुना करते हैं: 32 बकेट - 5 अंतिम बिट्स तय करते हैं कि प्रविष्टि कहां जाएगी।

जैसे कि इस प्रक्रिया को री-हैशिंग कहा जाता है। यह धीमा हो सकता है। वह यह है कि (जो लोग देखभाल करते हैं) हैशपॉप के रूप में "मजाक" है: तेज, तेज, तेज, नारा । अन्य कार्यान्वयन भी हैं - खोज की गई हैशमैप ...

अब UNTREEIFY_THRESHOLD फिर से हैशिंग के बाद खेलने में आता है। उस बिंदु पर, कुछ प्रविष्टियां इस डिब्बे से दूसरों तक जा सकती हैं (वे (n-1)&hashगणना में एक और बिट जोड़ते हैं - और जैसे अन्य बाल्टी में जा सकते हैं) और यह इस तक पहुंच सकता है UNTREEIFY_THRESHOLD। इस बिंदु पर यह बिन रखने के लिए भुगतान नहीं करता है red-black tree node, लेकिन LinkedListइसके बजाय, जैसे

 entry.next.next....

MIN_TREEIFY_CAPACITY एक बाल्टी में पेड़ में तब्दील होने से पहले बाल्टी की न्यूनतम संख्या है।


10

TreeNodeप्रविष्टियों का संग्रह करने के लिए एक वैकल्पिक तरीका है जो एकल बिन के हैं HashMap। पुराने कार्यान्वयन में एक बिन की प्रविष्टियों को एक लिंक की गई सूची में संग्रहीत किया गया था। जावा 8 में, यदि बिन में प्रविष्टियों की संख्या एक सीमा पार कर गई (TREEIFY_THRESHOLD ), तो उन्हें मूल लिंक की गई सूची के बजाय एक ट्री संरचना में संग्रहीत किया जाता है। यह एक अनुकूलन है।

कार्यान्वयन से:

/*
 * Implementation notes.
 *
 * This map usually acts as a binned (bucketed) hash table, but
 * when bins get too large, they are transformed into bins of
 * TreeNodes, each structured similarly to those in
 * java.util.TreeMap. Most methods try to use normal bins, but
 * relay to TreeNode methods when applicable (simply by checking
 * instanceof a node).  Bins of TreeNodes may be traversed and
 * used like any others, but additionally support faster lookup
 * when overpopulated. However, since the vast majority of bins in
 * normal use are not overpopulated, checking for existence of
 * tree bins may be delayed in the course of table methods.

बिल्कुल सच नहीं है । यदि वे गुजरते हैं TREEIFY_THRESHOLD और डिब्बे की कुल संख्या कम से कम है MIN_TREEIFY_CAPACITY। मैंने अपने उत्तर में इसे कवर करने की कोशिश की है ...
यूजीन

3

आपको इसकी कल्पना करने की आवश्यकता होगी: कहते हैं कि केवल हैशकोड () फ़ंक्शन के साथ एक वर्ग कुंजी है जो हमेशा एक ही मान को वापस करने के लिए ओवरराइड किया जाता है

public class Key implements Comparable<Key>{

  private String name;

  public Key (String name){
    this.name = name;
  }

  @Override
  public int hashCode(){
    return 1;
  }

  public String keyName(){
    return this.name;
  }

  public int compareTo(Key key){
    //returns a +ve or -ve integer 
  }

}

और फिर कहीं और, मैं एक HashMap में 9 प्रविष्टियां डाल रहा हूं, जिसमें सभी चाबियाँ इस वर्ग के उदाहरण हैं। जैसे

Map<Key, String> map = new HashMap<>();

    Key key1 = new Key("key1");
    map.put(key1, "one");

    Key key2 = new Key("key2");
    map.put(key2, "two");
    Key key3 = new Key("key3");
    map.put(key3, "three");
    Key key4 = new Key("key4");
    map.put(key4, "four");
    Key key5 = new Key("key5");
    map.put(key5, "five");
    Key key6 = new Key("key6");
    map.put(key6, "six");
    Key key7 = new Key("key7");
    map.put(key7, "seven");
    Key key8 = new Key("key8");
    map.put(key8, "eight");

//Since hascode is same, all entries will land into same bucket, lets call it bucket 1. upto here all entries in bucket 1 will be arranged in LinkedList structure e.g. key1 -> key2-> key3 -> ...so on. but when I insert one more entry 

    Key key9 = new Key("key9");
    map.put(key9, "nine");

  threshold value of 8 will be reached and it will rearrange bucket1 entires into Tree (red-black) structure, replacing old linked list. e.g.

                  key1
                 /    \
               key2   key3
              /   \   /  \

लिंक्डलिस्ट {O (n)} की तुलना में ट्री ट्रैवर्सल {O (लॉग एन)} तेज है और जैसे-जैसे बढ़ता है, अंतर अधिक महत्वपूर्ण होता जाता है।


यह संभवतः एक कुशल वृक्ष का निर्माण नहीं कर सकता क्योंकि इसके पास अपने हैशकोड के अलावा चाबियों की तुलना करने का कोई तरीका नहीं है, जो सभी समान हैं, और उनकी बराबरी का तरीका है, जो ऑर्डर करने में मदद नहीं करता है।
user253751

@immibis उनके हैशकोड आवश्यक रूप से समान नहीं हैं। वे काफी अलग हैं। कक्षाएं इसे लागू करते हैं, तो यह अतिरिक्त का उपयोग करेगा compareToसे ComparableidentityHashCodeएक और तंत्र है जो इसका उपयोग करता है।
माइकल

@ मिचेल इस उदाहरण में सभी हैशकोड आवश्यक रूप से समान हैं और वर्ग तुलनात्मक लागू नहीं करता है। पहचान नोड सही नोड खोजने में बेकार होगा।
user253751

@immibis आह हाँ, मैंने केवल इसे स्किम्ड किया है लेकिन आप सही हैं। इसलिए, जैसा Keyकि लागू नहीं होता है Comparable, identityHashCodeका उपयोग किया जाएगा :)
माइकल

@EmonMishra दुर्भाग्य से, बस दृश्य के लिए पर्याप्त नहीं होगा, मैंने अपने उत्तर में इसे कवर करने की कोशिश की है।
यूजीन

2

HashMap कार्यान्वयन में परिवर्तन JEP-180 के साथ जोड़ा गया था । उद्देश्य यह था:

मानचित्र प्रविष्टियों को संग्रहीत करने के लिए लिंक किए गए सूचियों की बजाय संतुलित पेड़ों का उपयोग करके java.util.HashMap के प्रदर्शन में सुधार करें। LinkedHashMap वर्ग में समान सुधार लागू करें

हालांकि शुद्ध प्रदर्शन एकमात्र लाभ नहीं है। इसकी रोकथाम भी होगी हैशडोस हमले , यदि उपयोगकर्ता इनपुट को संग्रहीत करने के लिए हैश मैप का उपयोग किया जाता है, क्योंकि बाल्टी में डेटा को संग्रहीत करने के लिए उपयोग किए जाने वाले लाल-काले पेड़ में ओ (लॉग एन) में सबसे खराब स्थिति सम्मिलन जटिलता है। एक निश्चित मापदंड पूरा होने के बाद पेड़ का उपयोग किया जाता है - यूजीन का जवाब देखें


-1

हैशमैप के आंतरिक कार्यान्वयन को समझने के लिए, आपको हैशिंग को समझने की आवश्यकता है। अपने सरलतम रूप में हाशिए करना, अपने गुणों पर किसी भी सूत्र / एल्गोरिथम को लागू करने के बाद किसी भी चर / वस्तु के लिए एक अद्वितीय कोड निर्दिष्ट करने का एक तरीका है।

एक सच्चे हैश फ़ंक्शन को इस नियम का पालन करना चाहिए -

"हैश फ़ंक्शन को समान या समान ऑब्जेक्ट्स पर लागू होने पर प्रत्येक और हर बार समान हैश कोड वापस करना चाहिए। दूसरे शब्दों में, दो समान वस्तुओं को लगातार एक ही हैश कोड का उत्पादन करना चाहिए। "


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