जावा हैशपैड कीसेट () पुनरावृति क्रम सुसंगत है?


81

मैं समझता हूं कि मानचित्र के कीसेट () विधि से लौटा गया सेट किसी विशेष आदेश की गारंटी नहीं देता है।

मेरा सवाल है, क्या यह कई पुनरावृत्तियों पर एक ही आदेश की गारंटी देता है । उदाहरण के लिए

Map<K,V> map = getMap();

for( K k : map.keySet() )
{
}

...

for( K k : map.keySet() )
{
}

उपरोक्त कोड में, यह मानते हुए कि नक्शा संशोधित नहीं है, क्या कुंजी पर पुनरावृत्ति उसी क्रम में होगी। सूर्य के jdk15 का उपयोग करना उसी क्रम में पुनरावृति करता है , लेकिन इससे पहले कि मैं इस व्यवहार पर निर्भर करता हूं, मैं यह जानना चाहूंगा कि क्या सभी जेडीके एक ही करेंगे।

संपादित करें

मैं उन उत्तरों से देखता हूं जो मैं उस पर निर्भर नहीं कर सकता। बहुत बुरा। मैं अपने आदेश की गारंटी के लिए कुछ नए संग्रह का निर्माण नहीं करने के साथ दूर होने की उम्मीद कर रहा था। मेरे कोड के माध्यम से पुनरावृति करने की आवश्यकता है, कुछ तर्क करते हैं, और फिर उसी क्रम से पुन: पुनरावृति करते हैं। मैं सिर्फ KeySet से एक नया ArrayList बनाऊंगा जो ऑर्डर की गारंटी देगा।


2
क्या आप नियंत्रित करते हैं कि मैप कार्यान्वयन को किस गेटपॉइंट () विधि से वापस किया जाता है?
एंड्री एडमॉविच

2
आप निश्चित रूप से अपना स्वयं का संग्रह बनाए बिना लगातार आदेश प्राप्त कर सकते हैं। अन्य लोगों के रूप में SortedMap देखें। इसलिए यदि आपका getMap () विधि इसके बजाय SortedMap देता है, तो कॉल करने वालों को लगातार ऑर्डर करने की उम्मीद होगी।
पीएसपीड

मेरा जवाब साबित करता है कि के क्रम .keySet()और .values()सुसंगत है। दुर्भाग्य से स्वीकृत उत्तर गलत है। @karoberts - क्या आप कृपया देख सकते हैं?
हर्षल पारेख

जवाबों:


52

यदि इसे एपीआई प्रलेखन में गारंटी नहीं दी जाती है, तो आपको इस पर निर्भर नहीं होना चाहिए। एक ही विक्रेता के JDK से व्यवहार JDK के एक रिलीज से दूसरे में भी बदल सकता है।

आप आसानी से सेट प्राप्त कर सकते हैं और फिर अपने आप को छाँट सकते हैं, है ना?


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

4
HashMap और HashSet यात्रा क्रम जावा 7 और जावा 8 के बीच बदल गया।
user100464

@KenLiu हाय मैं जावा के लिए बहुत नया हूं, क्या आप मुझे एक उदाहरण दे सकते हैं कि कैसे एक SortedMap प्राप्त करें? बहुत धन्यवाद।
सेसिलिया

क्या आप साबित कर सकते हैं कि यह असंगत है? सिर्फ इसलिए कि javadoc शब्द का उल्लेख "गारंटीकृत" नहीं है, इसका मतलब यह नहीं है कि यह असंगत है।
हर्षल पारेख

यह उत्तर गलत है। वे लगातार हैं। मैंने इसे यहां साबित किया है
हर्षल पारेख

58

यदि आप एक HashMap चाहते हैं, जिसका पुनरावृत्ति क्रम नहीं बदलता है, तो आप लिंक्डहैश मैप का उपयोग कर सकते हैं।

इसके अलावा आपको संग्रह के माध्यम से पुनरावृति करने पर हमेशा इसका उपयोग करना चाहिए। HashMap के एंट्रीसेट या कीसेट पर Iterating, LinkedHashMap की तुलना में बहुत धीमा है।


9

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

यदि आप एक विशिष्ट वर्ग को देख रहे हैं, जो मैप (हैशपैप, लिंक्डहाशमैप, ट्री मैप, आदि) को लागू करता है तो आप देख सकते हैं कि यह किस प्रकार लागू होता है कुंजी () फ़ंक्शन को निर्धारित करने के लिए कि स्रोत की जाँच करके व्यवहार क्या होगा। यह देखने के लिए कि क्या आप जिस प्रॉपर्टी की तलाश कर रहे हैं, वह देखने के लिए एल्गोरिथ्म पर वास्तव में बारीकी से विचार कर रही है (यानी, नक्शे के पुनरावृत्तियों के बीच कोई सम्मिलन / निष्कासन नहीं हुआ है)। उदाहरण के लिए, HashMap का स्रोत यहाँ है (JDK 6 खोलें): http://www.docjar.com/html/api/java/util/HashMap.java.html

यह व्यापक रूप से एक जेडीके से अगले तक भिन्न हो सकता है, इसलिए मैं निश्चित रूप से इस पर भरोसा नहीं करूंगा।

कहा जा रहा है, अगर निरंतर चलना आदेश कुछ ऐसी चीज है जिसकी आपको वास्तव में आवश्यकता है, तो आप एक LinkedHashMap की कोशिश कर सकते हैं।


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

7

मानचित्र के लिए एपीआई किसी भी आदेश की गारंटी नहीं देता है , यहां तक ​​कि एक ही वस्तु पर विधि के कई आह्वान के बीच भी।

व्यवहार में मैं बहुत आश्चर्यचकित होऊंगा अगर कई बाद के चालानों के लिए पुनरावृति क्रम बदल गया (यह मानते हुए कि नक्शा खुद के बीच में नहीं बदला) - लेकिन आपको (और एपीआई के अनुसार नहीं हो सकता है) इस पर भरोसा करना चाहिए।

EDIT - यदि आप पुनरावृति क्रम के अनुरूप होने पर निर्भर रहना चाहते हैं, तो आप एक SortedMap चाहते हैं, जो वास्तव में ये गारंटी प्रदान करता है।


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

5

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

आप उन्हें मानचित्र (या सेट) को रैप करने के लिए रैंडोमाइटर वापस कर सकते हैं जो तब आपको प्रत्येक लूप का उपयोग करने देगा।

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class Main
{
    private Main()
    {
    }

    public static void main(final String[] args)
    {
        final Map<String, String> items;

        items = new HashMap<String, String>();
        items.put("A", "1");
        items.put("B", "2");
        items.put("C", "3");
        items.put("D", "4");
        items.put("E", "5");
        items.put("F", "6");
        items.put("G", "7");

        display(items.keySet().iterator());
        System.out.println("---");

        display(items.keySet().iterator());
        System.out.println("---");

        display(new RandomIterator<String>(items.keySet().iterator()));
        System.out.println("---");

        display(new RandomIterator<String>(items.keySet().iterator()));
        System.out.println("---");
    }

    private static <T> void display(final Iterator<T> iterator)
    {
        while(iterator.hasNext())
        {
            final T item;

            item = iterator.next();
            System.out.println(item);
        }
    }
}

class RandomIterator<T>
    implements Iterator<T>
{
    private final Iterator<T> iterator;

    public RandomIterator(final Iterator<T> i)
    {
        final List<T> items;

        items = new ArrayList<T>();

        while(i.hasNext())
        {
            final T item;

            item = i.next();
            items.add(item);
        }

        Collections.shuffle(items);
        iterator = items.iterator();
    }

    public boolean hasNext()
    {
        return (iterator.hasNext());
    }

    public T next()
    {
        return (iterator.next());
    }

    public void remove()
    {
        iterator.remove();
    }
}

4

मैं LinkedHashMap बात से सहमत हूं। जब मैं समस्या का सामना कर रहा था तब मैं अपने निष्कर्षों और अनुभव को लगा रहा था, जब मैं चाबियों द्वारा हाशप को हल करने की कोशिश कर रहा था।

HashMap बनाने के लिए मेरा कोड:

HashMap<Integer, String> map;

@Before
public void initData() {
    map = new HashMap<>();

    map.put(55, "John");
    map.put(22, "Apple");
    map.put(66, "Earl");
    map.put(77, "Pearl");
    map.put(12, "George");
    map.put(6, "Rocky");

}

मेरे पास एक फंक्शन शो-रूम है जो नक्शे की प्रविष्टियों को प्रिंट करता है:

public void showMap (Map<Integer, String> map1) {
    for (Map.Entry<Integer,  String> entry: map1.entrySet()) {
        System.out.println("[Key: "+entry.getKey()+ " , "+"Value: "+entry.getValue() +"] ");

    }

}

अब जब मैं छँटाई करने से पहले नक्शा प्रिंट करता हूँ, तो यह अनुक्रम के बाद प्रिंट करता है:

Map before sorting : 
[Key: 66 , Value: Earl] 
[Key: 22 , Value: Apple] 
[Key: 6 , Value: Rocky] 
[Key: 55 , Value: John] 
[Key: 12 , Value: George] 
[Key: 77 , Value: Pearl] 

यह मूल रूप से उस क्रम से अलग है जिसमें मानचित्र कुंजी लगाई गई थी।

अब जब मैं इसे मानचित्र कुंजियों के साथ क्रमबद्ध करता हूं:

    List<Map.Entry<Integer, String>> entries = new ArrayList<>(map.entrySet());

    Collections.sort(entries, new Comparator<Entry<Integer, String>>() {

        @Override
        public int compare(Entry<Integer, String> o1, Entry<Integer, String> o2) {

            return o1.getKey().compareTo(o2.getKey());
        }
    });

    HashMap<Integer, String> sortedMap = new LinkedHashMap<>();

    for (Map.Entry<Integer, String> entry : entries) {
        System.out.println("Putting key:"+entry.getKey());
        sortedMap.put(entry.getKey(), entry.getValue());
    }

    System.out.println("Map after sorting:");

    showMap(sortedMap);

बाहर रखा गया है:

Sorting by keys : 
Putting key:6
Putting key:12
Putting key:22
Putting key:55
Putting key:66
Putting key:77
Map after sorting:
[Key: 66 , Value: Earl] 
[Key: 6 , Value: Rocky] 
[Key: 22 , Value: Apple] 
[Key: 55 , Value: John] 
[Key: 12 , Value: George] 
[Key: 77 , Value: Pearl] 

आप चाबियों के क्रम में अंतर देख सकते हैं। कुंजियों का क्रमबद्ध क्रम ठीक है, लेकिन कॉपी किए गए नक्शे की चाबियां फिर से पहले के नक्शे के उसी क्रम में हैं। मुझे नहीं पता कि यह कहना वैध है, लेकिन एक ही कुंजी के साथ दो हैशमैप के लिए, चाबियों का क्रम समान है। इसका तात्पर्य इस कथन से है कि कुंजियों के क्रम की गारंटी नहीं है, लेकिन मुख्य प्रविष्टि एल्गोरिथ्म की अंतर्निहित प्रकृति के कारण समान कुंजी वाले दो मानचित्रों के लिए समान हो सकता है यदि इस JVM संस्करण का HashMap कार्यान्वयन।

अब जब मैं HashMap को सॉर्ट की गई प्रविष्टियों को कॉपी करने के लिए LinkedHashMap का उपयोग करता हूं, तो मुझे वांछित परिणाम मिलता है (जो कि स्वाभाविक था, लेकिन वह बिंदु नहीं है। बिंदु हाशप की कुंजियों के क्रम के बारे में है)

    HashMap<Integer, String> sortedMap = new LinkedHashMap<>();

    for (Map.Entry<Integer, String> entry : entries) {
        System.out.println("Putting key:"+entry.getKey());
        sortedMap.put(entry.getKey(), entry.getValue());
    }

    System.out.println("Map after sorting:");

    showMap(sortedMap);

आउटपुट:

Sorting by keys : 
Putting key:6
Putting key:12
Putting key:22
Putting key:55
Putting key:66
Putting key:77
Map after sorting:
[Key: 6 , Value: Rocky] 
[Key: 12 , Value: George] 
[Key: 22 , Value: Apple] 
[Key: 55 , Value: John] 
[Key: 66 , Value: Earl] 
[Key: 77 , Value: Pearl] 


3

tl; डॉ । हाँ।


मैं के लिए विश्वास यात्रा के क्रम .keySet()और .values()सुसंगत (जावा 8) है।

प्रमाण 1 : हम HashMapयादृच्छिक कुंजियों और यादृच्छिक मूल्यों के साथ लोड करते हैं। हम कुंजियों HashMapका उपयोग कर .keySet()इसे लोड करते हैं और यह संबंधित मानों के लिए है LinkedHashMap(यह सम्मिलित कुंजियों और मूल्यों के क्रम को संरक्षित करेगा)। फिर हम .keySet()दोनों मैप्स और .values()दोनों मैप्स की तुलना करते हैं । यह हमेशा एक जैसा होता है, कभी असफल नहीं होता।

public class Sample3 {

    static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    static SecureRandom rnd = new SecureRandom();

    // from here: https://stackoverflow.com/a/157202/8430155
    static String randomString(int len){
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            sb.append(AB.charAt(rnd.nextInt(AB.length())));
        }
        return sb.toString();
    }

    public static void main(String[] args) throws Exception {
        for (int j = 0; j < 10; j++) {
            Map<String, String> map = new HashMap<>();
            Map<String, String> linkedMap = new LinkedHashMap<>();

            for (int i = 0; i < 1000; i++) {
                String key = randomString(8);
                String value = randomString(8);
                map.put(key, value);
            }

            for (String k : map.keySet()) {
                linkedMap.put(k, map.get(k));
            }

            if (!(map.keySet().toString().equals(linkedMap.keySet().toString()) &&
                  map.values().toString().equals(linkedMap.values().toString()))) {
                // never fails
                System.out.println("Failed");
                break;
            }
        }
    }
}

प्रमाण 2 : यहाँ से , वर्ग tableकी एक सरणी है Node<K,V>। हम जानते हैं कि किसी सरणी को पुनरावृत्त करना हर बार एक ही परिणाम देगा।

/**
 * The table, initialized on first use, and resized as
 * necessary. When allocated, length is always a power of two.
 * (We also tolerate length zero in some operations to allow
 * bootstrapping mechanics that are currently not needed.)
 */
transient Node<K,V>[] table;

इसके लिए जिम्मेदार वर्ग .values():

final class Values extends AbstractCollection<V> {
    
    // more code here

    public final void forEach(Consumer<? super V> action) {
        Node<K,V>[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
            int mc = modCount;
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e.value);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}

इसके लिए जिम्मेदार वर्ग .keySet():

final class KeySet extends AbstractSet<K> {

    // more code here

    public final void forEach(Consumer<? super K> action) {
        Node<K,V>[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
            int mc = modCount;
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e.key);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}

दोनों आंतरिक कक्षाओं को ध्यान से देखें। वे सिवाय इसके बहुत सुंदर हैं:

if (size > 0 && (tab = table) != null) {
    int mc = modCount;
    for (int i = 0; i < tab.length; ++i) {
        for (Node<K,V> e = tab[i]; e != null; e = e.next)
            action.accept(e.key);               <- from KeySet class
            // action.accept(e.value);          <- the only change from Values class
    }
    if (modCount != mc)
        throw new ConcurrentModificationException();
}

वे एक ही सरणी पर पुनरावृति tableसमर्थन करने के लिए .keySet()में KeySetवर्ग और .values()में Valuesवर्ग।


प्रमाण 3: यह उत्तर भी स्पष्ट रूप से बताता है - तो, हाँ, keySet (), मान (), और entrySet () आंतरिक संबंधित सूची के उपयोग के क्रम में मान लौटाता है।

इसलिए, .keySet()और .values()संगत हैं।


2

यह होना नहीं है। मैप का कीसेट फ़ंक्शन सेट को सेट करता है और सेट का इट्रेटर विधि अपने प्रलेखन में यह कहता है:

"इस सेट में तत्वों पर एक पुनरावृत्त लौटाता है। तत्वों को किसी विशेष क्रम में वापस नहीं किया जाता है (जब तक कि यह सेट कुछ वर्ग का एक उदाहरण है जो गारंटी प्रदान करता है)।"

इसलिए, जब तक आप उन वर्गों में से एक का उपयोग गारंटी के साथ कर रहे हैं, तब तक कोई नहीं है।


2

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


1

तार्किक रूप से, यदि अनुबंध कहता है कि "कोई विशेष आदेश की गारंटी नहीं है", और चूंकि "यह आदेश एक बार सामने आया था" एक विशेष आदेश है , तो जवाब है नहीं, आप इसे दो बार उसी तरह से बाहर आने पर निर्भर नहीं कर सकते।


0

आप कीसेट () विधि द्वारा लौटाए गए सेट इंस्टेंस को भी स्टोर कर सकते हैं और जब भी आपको उसी ऑर्डर की आवश्यकता हो तो इस इंस्टेंस का उपयोग कर सकते हैं।


1
है कि हालांकि, इसकी गारंटी? या प्रत्येक iterator()पुनरावृत्त को एक अलग पुनरावृत्ति क्रम के साथ एक पुनरावृत्ति करने के लिए कॉल कर सकता है , यहां तक ​​कि एक ही सेट पर भी?
मैट लीडीहोम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.