Java HashMap प्रदर्शन अनुकूलन / विकल्प


102

मैं एक बड़ा HashMap बनाना चाहता हूं लेकिन put()प्रदर्शन काफी अच्छा नहीं है। कोई विचार?

अन्य डेटा संरचना सुझावों का स्वागत है, लेकिन मुझे जावा मैप के लुकअप फीचर की आवश्यकता है:

map.get(key)

मेरे मामले में मैं 26 मिलियन प्रविष्टियों के साथ एक मानचित्र बनाना चाहता हूं। मानक जावा HashMap का उपयोग करते हुए पुट दर 2-3 मिलियन सम्मिलन के बाद असहनीय रूप से धीमी हो जाती है।

इसके अलावा, क्या किसी को पता है कि कुंजी के लिए विभिन्न हैश कोड वितरण का उपयोग करने से मदद मिल सकती है?

मेरा हैशकोड विधि:

byte[] a = new byte[2];
byte[] b = new byte[3];
...

public int hashCode() {
    int hash = 503;
    hash = hash * 5381 + (a[0] + a[1]);
    hash = hash * 5381 + (b[0] + b[1] + b[2]);
    return hash;
}

मैं यह सुनिश्चित करने के लिए कि समान वस्तुओं में समान हैशकोड है, के अतिरिक्त संपत्ति का उपयोग कर रहा हूं। सरणियाँ 0 - 51 के मानों के साथ बाइट्स हैं। मान केवल सरणी में एक बार उपयोग किए जाते हैं। ऑब्जेक्ट समान हैं यदि किसी सरणियों में समान मान (या तो क्रम में) होते हैं और वही b सरणी के लिए जाता है। तो a = {0,1} b = {45,12,33} और a = {1,0} b = {33,45,12} बराबर हैं।

संपादित करें, कुछ नोट्स:

  • 26 मिलियन प्रविष्टियों को संग्रहीत करने के लिए कुछ लोगों ने हैश मैप या अन्य डेटा संरचना का उपयोग करके आलोचना की है। मैं नहीं देख सकता कि यह अजीब क्यों लगेगा। यह मेरे लिए एक क्लासिक डेटा संरचनाओं और एल्गोरिदम समस्या की तरह दिखता है। मेरे पास 26 मिलियन आइटम हैं और मैं उन्हें जल्दी से सम्मिलित करने और उन्हें डेटा संरचना से देखने में सक्षम होना चाहता हूं: मुझे डेटा संरचना और एल्गोरिदम दें।

  • डिफ़ॉल्ट जावा HashMap की प्रारंभिक क्षमता को 26 मिलियन तक सेट करने से प्रदर्शन कम हो जाता है।

  • कुछ लोगों ने डेटाबेस का उपयोग करने का सुझाव दिया है, कुछ अन्य स्थितियों में जो निश्चित रूप से स्मार्ट विकल्प है। लेकिन मैं वास्तव में एक डेटा संरचनाओं और एल्गोरिदम सवाल पूछ रहा हूं, एक पूर्ण डेटाबेस एक अच्छा डेटास्ट्रक्चर समाधान की तुलना में ओवरकिल और बहुत धीमा होगा (सभी डेटाबेस के बाद सॉफ्टवेयर है लेकिन संचार और संभवतः डिस्क ओवरहेड होगा)।


29
यदि हैशपॉप धीमा हो जाता है, तो सभी संभावना है कि आपका हैश फ़ंक्शन पर्याप्त अच्छा नहीं है।
पास्कल कुओक

12
डॉक्टर, यह दर्द होता है जब मैं क्या इस
skaffman

12
यह एक बहुत अच्छा सवाल है; हैशिंग एल्गोरिदम क्यों मायने रखता है और प्रदर्शन पर उनका क्या असर हो सकता है, इसका एक अच्छा प्रदर्शन
21

12
ए की राशि में 0 से 102 की सीमा होती है और बी की राशि में 0 से 153 की सीमा होती है, इसलिए आपके पास केवल 15,606 संभावित हैश मान और समान हैशकोड के साथ औसतन 1,666 कुंजी हैं। आपको अपना हैशकोड बदलना चाहिए ताकि संभावित हैशकोड की संख्या कुंजियों की संख्या से बहुत अधिक हो।
पीटर लॉरी

6
मैंने मानसिक रूप से निर्धारित किया है कि आप टेक्सास होल्ड 'एम पोकर ;-)
Bacar

जवाबों:


56

जैसा कि कई लोगों ने बताया कि hashCode()विधि को दोष देना था। यह केवल 26 मिलियन अलग-अलग वस्तुओं के लिए लगभग 20,000 कोड उत्पन्न कर रहा था। यह हैश बाल्टी प्रति 1,300 वस्तुओं की एक औसत = बहुत खराब है। हालाँकि अगर मैं दो सरणियों को आधार 52 में एक संख्या में बदल देता हूं तो मुझे हर वस्तु के लिए एक अद्वितीय हैश कोड प्राप्त करने की गारंटी है:

public int hashCode() {       
    // assume that both a and b are sorted       
    return a[0] + powerOf52(a[1], 1) + powerOf52(b[0], 2) + powerOf52(b[1], 3) + powerOf52(b[2], 4);
}

public static int powerOf52(byte b, int power) {
    int result = b;
    for (int i = 0; i < power; i++) {
        result *= 52;
    }
    return result;
}

इस तरीके को hashCode()अनुबंध को पूरा करने के लिए सरणियों को क्रमबद्ध किया जाता है ताकि समान वस्तुओं में समान हैश कोड हो। पुरानी पद्धति के प्रयोग से 100,000 से अधिक ब्लॉक, 100,000 से 2,000,000 तक प्रति सेकंड पुट की औसत संख्या थी:

168350.17
109409.195
81344.91
64319.023
53780.79
45931.258
39680.29
34972.676
31354.514
28343.062
25562.371
23850.695
22299.22
20998.006
19797.799
18702.951
17702.434
16832.182
16084.52
15353.083

नई विधि का उपयोग कर देता है:

337837.84
337268.12
337078.66
336983.97
313873.2
317460.3
317748.5
320000.0
309704.06
310752.03
312944.5
265780.75
275540.5
264350.44
273522.97
270910.94
279008.7
276285.5
283455.16
289603.25

बहुत बेहतर। पुरानी विधि बहुत जल्दी से बंद हो जाती है जबकि नया एक अच्छा थ्रूपुट रखता है।


17
मेरा सुझाव है कि hashCodeविधि में सरणियों को संशोधित न करें । सम्मेलन द्वारा, hashCodeवस्तु की स्थिति को नहीं बदलता है। शायद इन्हें बनाने के लिए कंस्ट्रक्टर बेहतर जगह होगी।
माइकल मायर्स

मैं मानता हूं कि एरियर्स की छंटाई कंस्ट्रक्टर में होनी चाहिए। दिखाया गया कोड हैशकोड सेट करने के लिए कभी नहीं लगता है। कोड गिना जा रहा है के रूप में इस सरल किया जा सकता है: int result = a[0]; result = result * 52 + a[1]; //etc
rsp

मैं मानता हूं कि कंस्ट्रक्टर में छंटनी और फिर हैश कोड की गणना करना, जैसे कि mmyers और rsp सुझाव बेहतर है। मेरे मामले में मेरा समाधान स्वीकार्य है और मैं इस तथ्य को उजागर करना चाहता था कि hashCode()कार्य करने के लिए सरणियों को क्रमबद्ध किया जाना चाहिए ।
nash

3
ध्यान दें कि आप हैशकोड को कैश भी कर सकते हैं (और यदि आपकी वस्तु म्यूट है तो उचित रूप से अमान्य करें)।
नैट्स

1
बस java.util.Arrays.hashCode () का उपयोग करें । यह सरल है (अपने आप से लिखने और बनाए रखने के लिए कोई कोड नहीं), इसकी गणना संभवतः तेज (कम गुणा) है, और इसके हैश कोड का वितरण संभवतः और भी अधिक होगा।
jcsahnwaldt मोनिका

18

आपकी hashCode()विधि में एक बात मुझे ध्यान में आती है कि एरे में तत्वों का क्रम a[]और b[]कोई फर्क नहीं पड़ता। इस प्रकार (a[]={1,2,3}, b[]={99,100})हैश के समान मान होगा (a[]={3,1,2}, b[]={100,99})। वास्तव में सभी चाबियाँ k1और k2कहाँ sum(k1.a)==sum(k2.a)और sum(k1.b)=sum(k2.b)टकराव में परिणाम होगा। मेरा सुझाव है कि सरणी की प्रत्येक स्थिति के लिए एक वजन प्रदान करना:

hash = hash * 5381 + (c0*a[0] + c1*a[1]);
hash = hash * 5381 + (c0*b[0] + c1*b[1] + c3*b[2]);

जहां, c0, c1और c3कर रहे हैं विशिष्ट स्थिरांक (आप के लिए अलग अलग स्थिरांक उपयोग कर सकते हैं bयदि आवश्यक हो तो)। यह भी चीजों को थोड़ा और अधिक करना चाहिए।


हालाँकि मुझे यह भी जोड़ना चाहिए कि यह मेरे लिए काम नहीं करेगा क्योंकि मैं चाहता हूं कि जो संपत्ति अलग-अलग ऑर्डर में समान तत्वों के साथ मिलती है वही हैशकोड दें।
nash

5
उस स्थिति में, आपके पास 52C2 + 52C3 हैशकोड (मेरे कैलकुलेटर के अनुसार 23426) है, और एक हैशमैप नौकरी के लिए बहुत गलत उपकरण है।
20

वास्तव में इससे प्रदर्शन में वृद्धि होगी। अधिक टकराव eq हैशटेबल eq में कम प्रविष्टियाँ हैं। कम काम करना है। हैश नहीं है (जो ठीक लग रहा है) और न ही हैशटेबल (जो बहुत अच्छा काम करता है) मुझे यकीन है कि यह ऑब्जेक्ट निर्माण पर है जहां प्रदर्शन अपमानजनक है।
ऑस्करराइज

7
@ ऑस्कर - अधिक टकराव करने के लिए अधिक काम के बराबर होता है, क्योंकि अब आपको हैश श्रृंखला की एक रेखीय खोज करनी होगी। यदि आपके पास 26,000,000 अलग-अलग मान प्रति मान (), और हैशकोड () प्रति 26,000 अलग-अलग मान हैं, तो बकेट चेन में प्रत्येक में 1,000 ऑब्जेक्ट होंगे।
kdgregory

@ NAT0: आप यह कहते हुए दिखाई देते हैं कि आप चाहते हैं कि उनके पास समान हैशकोड हो, लेकिन साथ ही समान नहीं हो (जैसा कि बराबरी से परिभाषित किया गया हो) (विधि)। तुम ऐसा क्यों चाहेगो?
MAK

17

पास्कल के बारे में विस्तार से बताने के लिए: क्या आप समझते हैं कि हाशप कैसे काम करता है? आपके पास अपनी हैश तालिका में कुछ संख्याएँ हैं। प्रत्येक कुंजी के लिए हैश मान पाया जाता है, और फिर तालिका में प्रविष्टि के लिए मैप किया जाता है। यदि दो हैश मान एक ही प्रविष्टि के लिए मैप करते हैं - "हैश टक्कर" - हैशपॅप एक लिंक की गई सूची बनाता है।

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

इसलिए यदि आप प्रदर्शन की समस्याओं को देख रहे हैं, तो पहली चीज़ जो मैं देख रहा हूँ वह है: क्या मुझे हैश कोड का यादृच्छिक-रूप से वितरण मिल रहा है? यदि नहीं, तो आपको एक बेहतर हैश फ़ंक्शन की आवश्यकता है। खैर, इस मामले में "बेहतर" का अर्थ "मेरे विशेष डेटा के सेट के लिए बेहतर" हो सकता है। जैसे, मान लीजिए आप स्ट्रिंग्स के साथ काम कर रहे थे, और आपने हैश मान के लिए स्ट्रिंग की लंबाई ले ली। (यह नहीं है कि Java का String.hashCode कैसे काम करता है, लेकिन मैं सिर्फ एक सरल उदाहरण बना रहा हूं।) यदि आपके तार में व्यापक रूप से भिन्न लंबाई है, तो 1 से 10,000 तक, और काफी हद तक समान रूप से वितरित किए जाते हैं, कि यह बहुत अच्छा हो सकता है। हैश फंकशन। लेकिन अगर आपके तार सभी 1 या 2 वर्ण के हैं, तो यह बहुत खराब हैश फ़ंक्शन होगा।

संपादित करें: मुझे जोड़ना चाहिए: हर बार जब आप एक नई प्रविष्टि जोड़ते हैं, तो HashMap यह जाँचता है कि क्या यह एक डुप्लिकेट है। जब कोई हैश टकराव होता है, तो उसे उस स्लॉट पर मैप की गई प्रत्येक कुंजी के खिलाफ आने वाली कुंजी की तुलना करनी होती है। तो सबसे खराब स्थिति में जहां सब कुछ एक एकल स्लॉट में होता है, दूसरी कुंजी की तुलना पहली कुंजी से की जाती है, तीसरी कुंजी की तुलना # 1 और # 2 से की जाती है, चौथी कुंजी की तुलना # 1, # 2 और # 3 से की जाती है। , आदि जब तक आप कुंजी # 1 मिलियन प्राप्त करते हैं, तब तक आप एक ट्रिलियन तुलना से अधिक कर चुके होते हैं।

@ ऑस्कर: उम्म, मैं नहीं देखता कि यह कैसे "वास्तव में" नहीं है। यह "मुझे स्पष्ट करने दो" की तरह है। लेकिन हां, यह सच है कि यदि आप मौजूदा प्रविष्टि के समान कुंजी के साथ एक नई प्रविष्टि करते हैं, तो यह पहली प्रविष्टि को अधिलेखित कर देता है। इसका मतलब है कि जब मैंने अंतिम पैराग्राफ में डुप्लिकेट की तलाश के बारे में बात की थी: जब भी एक ही स्लॉट के लिए एक कुंजी हैश, अगर यह मौजूदा कुंजी की एक डुप्लिकेट है, या यदि वे सिर्फ एक ही स्लॉट में संयोग से हैं हैश फंकशन। मुझे नहीं पता है कि यह हैशपॉप का "पूरा बिंदु" है: मैं कहूंगा कि "संपूर्ण बिंदु" यह है कि आप तत्वों को कुंजी द्वारा जल्दी से पुनर्प्राप्त कर सकते हैं।

लेकिन फिर भी, यह "पूरे बिंदु" को प्रभावित नहीं करता है जिसे मैं बनाने की कोशिश कर रहा था: जब आपके पास दो कुंजी हैं - हाँ, अलग-अलग चाबियाँ, एक ही कुंजी फिर से दिखाई नहीं दे रही है - तालिका में उसी स्लॉट में नक्शा , HashMap एक लिंक की गई सूची बनाता है। फिर, क्योंकि यह देखने के लिए प्रत्येक नई कुंजी की जाँच करना है कि क्या यह वास्तव में किसी मौजूदा कुंजी का डुप्लिकेट है, प्रत्येक नई प्रविष्टि को जोड़ने का प्रयास करता है जो इस स्लॉट में मैप करता है, लिंक सूची का पीछा करना चाहिए ताकि प्रत्येक मौजूदा प्रविष्टि की जांच हो सके कि क्या यह देखने के लिए पहले देखी गई कुंजी की डुप्लिकेट है, या यदि यह एक नई कुंजी है।

मूल पोस्ट के लंबे समय बाद अपडेट करें

मुझे पोस्ट करने के 6 साल बाद सिर्फ इस सवाल पर अप-वोट मिला, जिसके कारण मुझे इस सवाल को फिर से पढ़ना पड़ा।

प्रश्न में दिया गया हैश फ़ंक्शन 26 मिलियन प्रविष्टियों के लिए एक अच्छा हैश नहीं है।

यह एक साथ जोड़ता है [0] + a [1] और b [0] + b [1] + b [2]। वह कहता है कि प्रत्येक बाइट का मान 0 से 51 तक है, इसलिए यह केवल (51 * 2 + 1) * (51 * 3 + 1) = 15,862 संभव हैश मान देता है। 26 मिलियन प्रविष्टियों के साथ, इसका अर्थ है प्रति हैश मान के बारे में 1639 प्रविष्टियां। यह बहुत सारी और बहुत सी टक्कर है, इसमें बहुत सी और बहुत सी अनुक्रमिक खोजों की आवश्यकता होती है।

ओपी का कहना है कि ए और सरणी बी के भीतर अलग-अलग आदेशों को समान माना जाना चाहिए, [[1,2], [3,4,5]]। बराबर ([[2,1], [5,3,4] ]), और इसलिए अनुबंध को पूरा करने के लिए उनके पास समान हैश कोड होना चाहिए। ठीक है। फिर भी, 15,000 से अधिक संभावित मूल्य हैं। उनका दूसरा प्रस्तावित हैश फंक्शन ज्यादा बेहतर है, एक व्यापक रेंज प्रदान करता है।

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

return a[0]+a[1]*52+b[0]*52*52+b[1]*52*52*52+b[2]*52*52*52*52;

जिसके कारण संकलनकर्ता एक बार संकलन समय पर गणना कर सकेगा; या कक्षा में 4 स्थिर स्थिरांक परिभाषित किए गए हैं।

साथ ही, हैश फ़ंक्शन के पहले ड्राफ्ट में कई गणनाएँ होती हैं जो आउटपुट की श्रेणी में जोड़ने के लिए कुछ नहीं करती हैं। ध्यान दें कि उन्होंने कक्षा से मानों पर विचार करने से पहले 5381 से कई बार हैश = 503 सेट किया। इसलिए ... वास्तव में वह हर मूल्य में 503 * 5381 जोड़ता है। यह क्या पूरा करता है? हर हैश मान में एक स्थिरांक जोड़ने से कुछ उपयोगी पूरा किए बिना सीपीयू चक्र जलता है। यहाँ पाठ: एक हैश फ़ंक्शन में जटिलता जोड़ना लक्ष्य नहीं है। लक्ष्य विभिन्न मूल्यों की एक विस्तृत श्रृंखला प्राप्त करना है, न कि केवल जटिलता के लिए जटिलता को जोड़ना है।


3
हाँ, एक खराब हैश फ़ंक्शन इस तरह के व्यवहार का परिणाम होगा। +1
हेनिंग

ज़रुरी नहीं। सूची केवल तभी बनाई गई है जब हैश समान है, लेकिन कुंजी अलग है । उदाहरण यदि एक स्ट्रिंग दे hashCode 2345 और और पूर्णांक एक ही hashCode 2345 देता है के लिए, तो पूर्णांक सूची में डाला क्योंकि है String.equals( Integer )है falseलेकिन अगर आपके पास एक ही वर्ग है (या कम से कम .equalsरिटर्न सही है) तो उसी प्रविष्टि का उपयोग किया जाता है। उदाहरण के लिए new String("one")और `नई स्ट्रिंग (" एक ") कुंजी के रूप में उपयोग किया जाता है, उसी प्रविष्टि का उपयोग करेगा। असल में यह है पूरे पहली जगह में HashMap की बात! खुद के लिए देखें: pastebin.com/f20af40b9
OscarRyz

3
@ ऑस्कर: मेरे मूल पोस्ट से संबंधित मेरे उत्तर को देखें।
जेई

मुझे पता है कि यह एक बहुत पुराना धागा है, लेकिन यहाँ "टकराव" शब्द का एक संदर्भ है क्योंकि यह हैश कोड: लिंक से संबंधित है । जब आप उसी कुंजी के साथ एक और मान डालकर हैशमैप में एक मान को प्रतिस्थापित करते हैं, तो इसे टक्कर नहीं कहा जाता है
ताहिर अख्तर

@ ताहिर बिल्कुल। शायद मेरी पोस्ट खराब शब्दों में थी। स्पष्टीकरण के लिए धन्यवाद।
Jay

7

मेरा पहला विचार यह सुनिश्चित करना है कि आप अपने HashMap को उचित रूप से आरंभ कर रहे हैं। HashMap के लिए JavaDocs से :

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

इसलिए यदि आप बहुत छोटे हैशमैप के साथ शुरुआत कर रहे हैं, तो हर बार इसे आकार बदलने की आवश्यकता होती है, सभी हैश को फिर से प्रतिष्ठित किया जाता है ... जो कि आप महसूस कर रहे होंगे जब आप 2-3 मिलियन सम्मिलन बिंदु पर पहुंचते हैं।


मुझे नहीं लगता कि वे पुन: प्रतिष्ठित हैं, कभी। टेबल का आकार बढ़ाया जाता है, हैश रखा जाता है।
हेनिंग

हशमप बस थोड़ी-सी समझदारी और हर प्रविष्टि के लिए करता है: newIndex = storeHash & newLength;
हेनिंग

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

जय, हाँ - वास्तव में खराब शब्दकरण, और आपने क्या कहा। :)
डेलफ्यूगो

1
@delfuego और @ nash0: हां, तत्वों की संख्या के बराबर प्रारंभिक क्षमता निर्धारित करने से प्रदर्शन कम हो जाता है क्योंकि आप लाखों टकराव कर रहे हैं और इस प्रकार आप केवल उस क्षमता की थोड़ी मात्रा का उपयोग कर रहे हैं। यहां तक ​​कि अगर आप सभी उपलब्ध प्रविष्टियों का उपयोग करते हैं, तो एक ही क्षमता स्थापित करने से यह सबसे खराब हो जाएगा!, क्योंकि लोड कारक के कारण अधिक स्थान का अनुरोध किया जाएगा। आपको initialcapactity = maxentries/loadcapacity(जैसे 30M, 0.95 26M प्रविष्टियों के लिए) का उपयोग करना होगा , लेकिन यह आपका मामला नहीं है, क्योंकि आप उन सभी टकरावों का सामना कर रहे हैं जिनका आप केवल 20k या उससे कम उपयोग कर रहे हैं।
ऑस्करराइज

7

मैं तीन-आयामी दृष्टिकोण सुझाता हूँ:

  1. जावा को अधिक मेमोरी के साथ चलाएं: java -Xmx256Mउदाहरण के लिए 256 मेगाबाइट के साथ चलाना। जरूरत पड़ने पर और उपयोग करें और आपके पास बहुत सारी रैम है।

  2. किसी अन्य पोस्टर द्वारा सुझाए गए अपने हैश मानों को कैश करें, इसलिए प्रत्येक ऑब्जेक्ट केवल एक बार अपने हैश मान की गणना करता है।

  3. एक बेहतर हैशिंग एल्गोरिथ्म का उपयोग करें। आपके द्वारा पोस्ट किया गया वही हैश लौटाएगा जहाँ a = {0, 1} जैसा कि वह होगा जहाँ a = {1, 0}, बाकी सब समान होगा।

जावा आपको मुफ्त में क्या देता है, का उपयोग करें।

public int hashCode() {
    return 31 * Arrays.hashCode(a) + Arrays.hashCode(b);
}

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


रैम इस तरह के नक्शे और सरणियों के लिए छोटे होने का रास्ता हो सकता है, इसलिए मुझे पहले से ही एक स्मृति सीमा समस्या का संदेह था।
रेनेस

7

"पर / बंद विषय" के ग्रे क्षेत्र में हो रही है, लेकिन ऑस्कर रेयेस के सुझाव के बारे में भ्रम को खत्म करने के लिए आवश्यक है कि अधिक हैश टकराव एक अच्छी बात है क्योंकि यह हाशप में तत्वों की संख्या को कम करता है। मैं गलत समझ सकता हूं कि ऑस्कर क्या कह रहा है, लेकिन मैं केवल एक ही नहीं लगता: kdgregory, delfuego, Nash0, और मैं सभी को एक ही (गलत) समझ साझा करता हूं।

यदि मैं समझता हूं कि ऑस्कर उसी हैशकोड के साथ उसी वर्ग के बारे में क्या कह रहा है, तो वह प्रस्ताव दे रहा है कि किसी दिए गए हैशकोड के साथ केवल एक वर्ग का एक उदाहरण हैशपॉप में डाला जाएगा। उदाहरण के लिए, यदि मेरे पास 1 के हैशकोड के साथ SomeClass का एक उदाहरण है और 1 के हैशकोड के साथ SomeClass के दूसरे उदाहरण में, SomeClass का केवल एक उदाहरण डाला गया है।

Http://pastebin.com/f20af40b9 पर जावा पास्टेबिन उदाहरण से यह संकेत मिलता है कि उपरोक्त सही ढंग से संक्षेप में बताया गया है कि ऑस्कर का प्रस्ताव क्या है।

किसी भी समझ या गलतफहमी के बावजूद, क्या होता है एक ही वर्ग के विभिन्न उदाहरणों को केवल एक बार हैशपॉप में डाला नहीं जाता है यदि उनके पास एक ही हैशकोड है - तब तक नहीं जब तक कि यह निर्धारित नहीं किया जाता है कि चाबियाँ समान हैं या नहीं। हैशकोड अनुबंध के लिए आवश्यक है कि समान वस्तुओं में समान हैशकोड हो; हालाँकि, यह आवश्यक नहीं है कि असमान वस्तुओं में अलग-अलग हैशकोड हों (हालाँकि यह अन्य कारणों से वांछनीय हो सकता है) [1]।

Pastebin.com/f20af40b9 उदाहरण (जो ऑस्कर कम से कम दो बार संदर्भित करता है) अनुसरण करता है, लेकिन प्रिंटलाइन के बजाय JUnit मुखर का उपयोग करने के लिए थोड़ा संशोधित किया गया है। इस उदाहरण का उपयोग इस प्रस्ताव का समर्थन करने के लिए किया जाता है कि समान हैशकोड टकराव का कारण बनते हैं और जब कक्षाएं समान होती हैं तो केवल एक प्रविष्टि बनाई जाती है (जैसे, इस विशिष्ट मामले में केवल एक स्ट्रिंग):

@Test
public void shouldOverwriteWhenEqualAndHashcodeSame() {
    String s = new String("ese");
    String ese = new String("ese");
    // same hash right?
    assertEquals(s.hashCode(), ese.hashCode());
    // same class
    assertEquals(s.getClass(), ese.getClass());
    // AND equal
    assertTrue(s.equals(ese));

    Map map = new HashMap();
    map.put(s, 1);
    map.put(ese, 2);
    SomeClass some = new SomeClass();
    // still  same hash right?
    assertEquals(s.hashCode(), ese.hashCode());
    assertEquals(s.hashCode(), some.hashCode());

    map.put(some, 3);
    // what would we get?
    assertEquals(2, map.size());

    assertEquals(2, map.get("ese"));
    assertEquals(3, map.get(some));

    assertTrue(s.equals(ese) && s.equals("ese"));
}

class SomeClass {
    public int hashCode() {
        return 100727;
    }
}

हालाँकि, हैशकोड पूरी कहानी नहीं है। पास्टबिन उदाहरण की उपेक्षा क्या तथ्य यह है कि दोनों समान हैं sऔर eseसमान हैं: वे दोनों "स्ट्रिंग" हैं। इस प्रकार, कुंजी का उपयोग करके sया eseउसके "ese"रूप में मानचित्र की सामग्री को सम्मिलित करना या प्राप्त करना सभी समान हैं s.equals(ese) && s.equals("ese")

एक दूसरा परीक्षण दर्शाता है कि यह निष्कर्ष निकालना गलत है कि एक ही वर्ग पर समान हैशकोड कारण है - जब परीक्षण एक में बुलाया जाता s -> 1है ese -> 2तो मूल्य -> अधिलेखित हो map.put(ese, 2)जाता है। परीक्षण दो में, sऔर eseअभी भी एक ही हैशकोड (जैसा कि सत्यापित है assertEquals(s.hashCode(), ese.hashCode());) और वे एक ही वर्ग हैं। हालांकि, sऔर इस परीक्षण में उदाहरण eseहैं MyString, जावा Stringउदाहरण नहीं - इस परीक्षण के लिए एकमात्र अंतर के बराबर प्रासंगिक होने के साथ: String s equals String eseऊपर एक परीक्षण में, जबकि MyStrings s does not equal MyString eseपरीक्षण दो में:

@Test
public void shouldInsertWhenNotEqualAndHashcodeSame() {
    MyString s = new MyString("ese");
    MyString ese = new MyString("ese");
    // same hash right?
    assertEquals(s.hashCode(), ese.hashCode());
    // same class
    assertEquals(s.getClass(), ese.getClass());
    // BUT not equal
    assertFalse(s.equals(ese));

    Map map = new HashMap();
    map.put(s, 1);
    map.put(ese, 2);
    SomeClass some = new SomeClass();
    // still  same hash right?
    assertEquals(s.hashCode(), ese.hashCode());
    assertEquals(s.hashCode(), some.hashCode());

    map.put(some, 3);
    // what would we get?
    assertEquals(3, map.size());

    assertEquals(1, map.get(s));
    assertEquals(2, map.get(ese));
    assertEquals(3, map.get(some));
}

/**
 * NOTE: equals is not overridden so the default implementation is used
 * which means objects are only equal if they're the same instance, whereas
 * the actual Java String class compares the value of its contents.
 */
class MyString {
    String i;

    MyString(String i) {
        this.i = i;
    }

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

बाद की एक टिप्पणी के आधार पर, ऑस्कर ने जो कुछ भी पहले कहा था उसे उलटा करने के लिए लगता है और बराबरी के महत्व को स्वीकार करता है। हालांकि, यह अभी भी धारणा है कि समान है जो मायने रखता है, न कि "एक ही वर्ग", अस्पष्ट है (मेरा जोर)

"वास्तव में नहीं। सूची केवल तभी बनाई गई है जब हैश समान है, लेकिन कुंजी अलग है। उदाहरण के लिए यदि कोई स्ट्रिंग हैशकोड 2345 और इंटीगर समान हैशकोड 2345 देता है, तो पूर्णांक सूची में डाला जाता है क्योंकि स्ट्रिंग। बराबर (पूर्णांक) गलत है। लेकिन यदि आपके पास एक ही वर्ग है (या कम से कम-असमान रिटर्न सही है) तो उसी प्रविष्टि का उपयोग किया जाता है। उदाहरण के लिए नया स्ट्रिंग ("एक") और `नया स्ट्रिंग (" एक ") का उपयोग किया जाता है। कुंजियाँ, एक ही प्रविष्टि का उपयोग करेंगी। वास्तव में यह पहली बार में हाशप का पूर्ण बिंदु है! अपने लिए देखें: pastebin.com/f20af40b9 - ऑस्कर रेयेस "

पहले की टिप्पणियों के अनुसार समान वर्ग और समान हैशकोड के महत्व को स्पष्ट रूप से संबोधित करते हैं, जिसमें बराबरी का कोई उल्लेख नहीं है:

"@delfuego: खुद के लिए देखें: pastebin.com/f20af40b9 तो, इस प्रश्न में उसी वर्ग का उपयोग किया जा रहा है (एक मिनट रुको, उसी कक्षा का उपयोग सही किया जा रहा है?) जिसका अर्थ है कि जब समान हैश का उपयोग किया जाता है? का उपयोग किया जाता है और प्रविष्टियों की "सूची" नहीं है। - ऑस्कर रेयेस "

या

"वास्तव में यह प्रदर्शन को बढ़ाएगा। अधिक टकराव हैशटेबल ईक में कम प्रविष्टियां eq। कम काम करने के लिए। हैश नहीं है (जो ठीक दिखता है) और न ही हैशटेबल (जो बहुत अच्छा काम करता है) मैं शर्त लगा सकता हूं कि यह ऑब्जेक्ट पर है। निर्माण जहां प्रदर्शन अपमानजनक है। - ऑस्कर रेयेस "

या

"@kdgregory: हाँ, लेकिन केवल अगर टक्कर अलग-अलग वर्गों के साथ होती है, तो एक ही कक्षा के लिए (जो मामला है) एक ही प्रविष्टि का उपयोग किया जाता है। - ऑस्कर रेयेस"

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


[१] - प्रभावी जावा से, जोशुआ बलोच द्वारा दूसरा संस्करण :

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

  • यदि दो वस्तुएँ समान s (Obj ect) विधि के अनुसार समान हैं, तो दो वस्तुओं में से प्रत्येक पर हैशकोड विधि को कॉल करके एक ही पूर्णांक परिणाम का उत्पादन करना होगा।

  • यह आवश्यक नहीं है कि यदि दो वस्तुएं समान s (ऑब्जेक्ट) विधि के अनुसार असमान हैं, तो दो वस्तुओं में से प्रत्येक पर हैशकोड विधि को कॉल करके अलग पूर्णांक परिणाम उत्पन्न करना होगा। हालांकि, प्रोग्रामर को यह पता होना चाहिए कि असमान वस्तुओं के लिए अलग पूर्णांक परिणाम बनाने से हैश तालिकाओं के प्रदर्शन में सुधार हो सकता है।


5

यदि आपके पोस्ट किए गए हैशकोड में सरणियाँ बाइट्स हैं, तो आप संभवतः बहुत सारे डुप्लिकेट के साथ समाप्त हो जाएंगे।

[०] + ए [१] हमेशा ० और ५१२ के बीच रहेगा। बी के जोड़ने से हमेशा ० और a६० के बीच की संख्या होगी। उन लोगों को गुणा करें और आपको ४००,००० अद्वितीय संयोजनों की एक ऊपरी सीमा मिलती है, यह मानते हुए कि आपका डेटा पूरी तरह से वितरित है। प्रत्येक बाइट के हर संभव मूल्य के बीच। यदि आपका डेटा नियमित रूप से है, तो आपके पास इस विधि के बहुत कम अद्वितीय आउटपुट हैं।


4

HashMap की प्रारंभिक क्षमता है और HashMap का प्रदर्शन बहुत हद तक हैशकोड पर निर्भर करता है जो अंतर्निहित वस्तुओं का उत्पादन करता है।

दोनों को जोड़ने की कोशिश करें।


4

यदि कुंजियों के पास कोई पैटर्न है तो आप मानचित्र को छोटे मानचित्रों में विभाजित कर सकते हैं और एक इंडेक्स मैप कर सकते हैं।

उदाहरण: कुंजी: 1,2,3, .... n 1 मिलियन प्रत्येक के 28 नक्शे। इंडेक्स मैप: 1-1,000,000 -> मैप 1 1,000,000-2,000,000 -> मैप 2

तो आप दो लुकअप कर रहे होंगे लेकिन कुंजी सेट 1,000,000 बनाम 28,000,000 होगा। आप इसे स्टिंग पैटर्न के साथ भी आसानी से कर सकते हैं।

यदि चाबियाँ पूरी तरह से यादृच्छिक हैं तो यह काम नहीं करेगा


1
यहां तक ​​कि अगर चाबियाँ यादृच्छिक हैं, तो आप उस कुंजी-मान को संग्रहीत करने के लिए मानचित्र का चयन करने के लिए (key.hashCode ()% 28) का उपयोग कर सकते हैं।
जूहा सिरजला

4

यदि आप जिस दो बाइट सरणियों का उल्लेख करते हैं, वह आपकी पूरी कुंजी है, मान 0-51 की श्रेणी में हैं, अनोखा और ए और बी सरणियों के भीतर का आदेश महत्वहीन है, मेरा गणित मुझे बताता है कि केवल लगभग 26 मिलियन संभावित परमिट हैं संभावना है कि आप सभी संभावित कुंजियों के लिए मानों के साथ मानचित्र भरने की कोशिश कर रहे हैं।

इस स्थिति में, आपके डेटा स्टोर से मानों को भरने और पुनर्प्राप्त करने के दोनों निश्चित रूप से बहुत तेज़ होंगे यदि आप किसी HashMap के बजाय एक सरणी का उपयोग करते हैं और इसे 0 से 25989599 तक अनुक्रमित करते हैं।


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

4

मुझे यहाँ आने में देर हो रही है, लेकिन कुछ लोग बड़े मानचित्रों के बारे में टिप्पणी करते हैं:

  1. जैसा कि अन्य पोस्ट में लंबाई पर चर्चा की गई है, एक अच्छे हैशकोड () के साथ, मानचित्र में 26M प्रविष्टियां कोई बड़ी बात नहीं है।
  2. हालांकि, यहां संभावित रूप से छिपा हुआ मुद्दा विशाल मानचित्रों का GC प्रभाव है।

मैं एक धारणा बना रहा हूं कि ये नक्शे लंबे समय तक जीवित हैं। यानी आप उन्हें पॉप्युलेट करते हैं और वे ऐप की अवधि के लिए इधर-उधर चिपके रहते हैं। मैं यह भी मान रहा हूं कि ऐप अपने आप ही लंबे समय तक जीवित है - किसी प्रकार के सर्वर की तरह।

Java HashMap में प्रत्येक प्रविष्टि के लिए तीन वस्तुओं की आवश्यकता होती है: कुंजी, मूल्य और प्रविष्टि जो उन्हें एक साथ जोड़ती है। तो नक्शे में 26M प्रविष्टियों का मतलब 26M * 3 == 78M ऑब्जेक्ट है। यह तब तक ठीक है जब तक आप एक पूर्ण जीसी को नहीं मारते। तब आपको एक विश्वव्यापी समस्या मिल गई है। GC प्रत्येक 78M ऑब्जेक्ट को देखेगा और निर्धारित करेगा कि वे सभी जीवित हैं। 78M + ऑब्जेक्ट सिर्फ देखने के लिए बहुत सी वस्तुएं हैं। यदि आपका ऐप कभी-कभार लंबा (शायद कई सेकंड) रुक सकता है, तो कोई समस्या नहीं है। यदि आप किसी भी विलंबता की गारंटी प्राप्त करने की कोशिश कर रहे हैं, तो आप एक प्रमुख मुद्दा हो सकते हैं (यदि आप विलंबता की गारंटी चाहते हैं, तो जावा को चुनने के लिए मंच नहीं है :)) यदि आपके नक्शों में दिए गए मान जल्दी मंथन करते हैं तो आप बार-बार पूर्ण संग्रह के साथ समाप्त हो सकते हैं। जो समस्या को बहुत बढ़ा देता है।

मैं इस मुद्दे के लिए एक महान समाधान का पता नहीं है। विचार:

  • कभी-कभी जीसी और ढेर के आकार को "ज्यादातर" रोकने के लिए पूर्ण जीसी को रोकना संभव है।
  • यदि आपकी मानचित्र सामग्री बहुत कुछ मंथन करती है तो आप Javolution के FastMap की कोशिश कर सकते हैं - यह एंट्री ऑब्जेक्ट्स को पूल कर सकता है, जो पूर्ण संग्रह की आवृत्ति को कम कर सकता है
  • आप अपना स्वयं का मानचित्र बना सकते हैं और बाइट पर स्पष्ट मेमोरी प्रबंधन कर सकते हैं [(अर्थात एक ही बाइट में लाखों वस्तुओं को क्रमबद्ध करके अधिक अनुमानित लेटेंसी के लिए व्यापार सीपीयू [] - उघ!)
  • इस भाग के लिए जावा का उपयोग न करें - सॉकेट के ऊपर किसी प्रकार की प्रेडिक्टेबल इन-मेमोरी डीबी पर बात करें
  • आशा है कि नया G1 कलेक्टर मदद करेगा (मुख्य रूप से उच्च-मंथन मामले पर लागू होता है)

जावा में विशाल मानचित्रों के साथ बहुत समय बिताने वाले किसी व्यक्ति के कुछ विचार।



3

मेरे मामले में मैं 26 मिलियन प्रविष्टियों के साथ एक मानचित्र बनाना चाहता हूं। मानक जावा HashMap का उपयोग करते हुए पुट दर 2-3 मिलियन सम्मिलन के बाद असहनीय रूप से धीमी हो जाती है।

मेरे प्रयोग से (2009 में छात्र परियोजना):

  • मैंने 1 से 100.000 तक 100.000 नोड्स के लिए एक रेड ब्लैक ट्री बनाया। इसमें 785.68 सेकंड (13 मिनट) लगे। और मैं 1 मिलियन नोड्स के लिए RBTree का निर्माण करने में विफल रहा (जैसे हाशप के साथ आपके परिणाम)।
  • "प्राइम ट्री", मेरे एल्गोरिथ्म डेटा संरचना का उपयोग करना। मैं 21.29 सेकंड (RAM: 1.97Gb) के भीतर 10 मिलियन नोड्स के लिए एक पेड़ / नक्शा बना सकता हूं। खोज कुंजी-मूल्य लागत हे (1) है।

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


तो, #PrimeTree क्या है? संक्षेप में, यह बाइनरी ट्री की तरह एक पेड़ डेटा संरचना है, जिसमें शाखा संख्याएं प्राइम नंबर ("2" -बिनाइल के बजाय) हैं।


क्या आप कृपया कुछ लिंक या कार्यान्वयन साझा कर सकते हैं?
बेंज



1

क्या आपने ऐसा करने के लिए एक एम्बेड किए गए डेटाबेस का उपयोग करने पर विचार किया है। बर्कले DB को देखो । यह ओपेन-सोर्स है, जिसका स्वामित्व अब ओरेकल के पास है।

यह Key-> वैल्यू पेयर के रूप में सब कुछ स्टोर करता है, यह RDBMS नहीं है। और इसका उद्देश्य तेजी से होना है।


2
बर्कले डीबी क्रमिक / आईओ ओवरहेड के कारण प्रविष्टियों की इस संख्या के लिए पर्याप्त तेजी से पास नहीं है; यह एक हैशमैप से तेज कभी नहीं हो सकता है और ओपी दृढ़ता के बारे में परवाह नहीं करता है। आपका सुझाव अच्छा नहीं है।
oxbow_lakes

1

पहले आपको यह जांचना चाहिए कि आप मानचित्र का सही उपयोग कर रहे हैं, चाबियों के लिए अच्छा हैशकोड () विधि, मानचित्र की प्रारंभिक क्षमता, सही नक्शा कार्यान्वयन आदि जैसे कई अन्य उत्तर बताते हैं।

फिर मैं एक प्रोफाइलर का उपयोग करके यह देखने का सुझाव दूंगा कि वास्तव में क्या हो रहा है और निष्पादन समय कहां खर्च होता है। उदाहरण के लिए, हैशकोड () विधि को कई बार अरबों के लिए निष्पादित किया जाता है?

कि, कैसे के बारे में की तरह कुछ का उपयोग कर मदद नहीं करता है, तो ehcache या memcached ? हां, वे कैशिंग के लिए उत्पाद हैं, लेकिन आप उन्हें कॉन्फ़िगर कर सकते हैं ताकि उनके पास पर्याप्त क्षमता हो और कैश स्टोरेज से किसी भी मान को बेदखल न करें।

एक अन्य विकल्प कुछ डेटाबेस इंजन होगा जो पूर्ण SQL RDBMS की तुलना में हल्का होता है। बर्कले डीबी की तरह कुछ , शायद।

ध्यान दें, कि मुझे व्यक्तिगत रूप से इन उत्पादों के प्रदर्शन का कोई अनुभव नहीं है, लेकिन वे कोशिश के काबिल हो सकते हैं।


1

आप गणना की गई हैश कोड को मुख्य ऑब्जेक्ट को कैश करने का प्रयास कर सकते हैं।

कुछ इस तरह:

public int hashCode() {
  if(this.hashCode == null) {
     this.hashCode = computeHashCode();
  }
  return this.hashCode;
}

private int computeHashCode() {
   int hash = 503;
   hash = hash * 5381 + (a[0] + a[1]);
   hash = hash * 5381 + (b[0] + b[1] + b[2]);
   return hash;
}

बेशक आपको सावधान रहना होगा कि पहली बार हैशकोड की गणना के बाद कुंजी की सामग्री को बदलने के लिए नहीं।

संपादित करें: ऐसा लगता है कि कैशिंग में कोड मान सार्थक नहीं है जब आप प्रत्येक कुंजी को केवल एक बार एक नक्शे में जोड़ रहे हैं। किसी अन्य स्थिति में यह उपयोगी हो सकता है।


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

1

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

यदि आपके पास हमेशा मान 0..51 है, तो उन मानों में से प्रत्येक का प्रतिनिधित्व करने के लिए 6 बिट्स होंगे। यदि आपके पास हमेशा 5 मान होते हैं, तो आप बाईं-पाली और परिवर्धन के साथ 30-बिट हैशकोड बना सकते हैं:

    int code = a[0];
    code = (code << 6) + a[1];
    code = (code << 6) + b[0];
    code = (code << 6) + b[1];
    code = (code << 6) + b[2];
    return code;

बाईं-शिफ्ट तेज़ है, लेकिन आपको हैशकोड के साथ छोड़ देगा जो समान रूप से वितरित नहीं किए गए हैं (क्योंकि 6 बिट्स का मतलब 0..6% है)। एक विकल्प हैश को 51 से गुणा करना और प्रत्येक मान जोड़ना। यह अभी भी पूरी तरह से वितरित नहीं किया जाएगा (उदाहरण के लिए, {2,0} और {1,52} टकराएगा), और शिफ्ट की तुलना में धीमा होगा।

    int code = a[0];
    code *= 51 + a[1];
    code *= 51 + b[0];
    code *= 51 + b[1];
    code *= 51 + b[2];
    return code;

@kdgregory: मैंने कहीं और "अधिक टकराव का मतलब है कि अधिक काम का अर्थ" के बारे में जवाब दिया :)
OscarRyz

1

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

यदि आपको आगे भी अनुकूलन करने की आवश्यकता है:

आपके विवरण से, केवल (५२ * ५१ / २) * (५२ (५१ * ५० / ६०) = २ ९ ३०४६०० अलग-अलग कुंजी हैं (जिनमें से २६०००००० यानि लगभग ९ ०% मौजूद होंगी)। इसलिए, आप किसी भी टकराव के बिना हैश फ़ंक्शन को डिज़ाइन कर सकते हैं, और अपने डेटा को होल्ड करने के लिए हैशमैप के बजाय एक सरल सरणी का उपयोग कर सकते हैं, मेमोरी की खपत को कम कर सकते हैं और लुकअप गति बढ़ा सकते हैं:

T[] array = new T[Key.maxHashCode];

void put(Key k, T value) {
    array[k.hashCode()] = value;

T get(Key k) {
    return array[k.hashCode()];
}

(आम तौर पर, एक कुशल, टक्कर-मुक्त हैश फ़ंक्शन को डिज़ाइन करना असंभव है जो अच्छी तरह से क्लस्टर करता है, यही कारण है कि एक हैशपॉप टकराव को सहन करेगा, जो कुछ ओवरहेड को उकसाता है)

मान लिया aऔर bहल कर रहे हैं, तो आप निम्न हैश फ़ंक्शन का उपयोग कर सकते हैं:

public int hashCode() {
    assert a[0] < a[1]; 
    int ahash = a[1] * a[1] / 2 
              + a[0];

    assert b[0] < b[1] && b[1] < b[2];

    int bhash = b[2] * b[2] * b[2] / 6
              + b[1] * b[1] / 2
              + b[0];
    return bhash * 52 * 52 / 2 + ahash;
}

static final int maxHashCode = 52 * 52 / 2 * 52 * 52 * 52 / 6;  

मुझे लगता है कि यह टक्कर-मुक्त है। यह साबित करना गणितीय रूप से इच्छुक पाठक के लिए एक अभ्यास के रूप में छोड़ दिया गया है।


1

में प्रभावी जावा: प्रोग्रामिंग भाषा गाइड (जावा सीरीज)

अध्याय 3 आप हैशकोड () की गणना करते समय अच्छे नियमों का पालन कर सकते हैं।

विशेष रूप से:

यदि फ़ील्ड एक सरणी है, तो मान लें कि प्रत्येक तत्व एक अलग फ़ील्ड था। अर्थात्, इन नियमों को पुनरावर्ती रूप से लागू करके प्रत्येक महत्वपूर्ण तत्व के लिए एक हैश कोड की गणना करें और इन मानों को प्रति चरण 2.b पर संयोजित करें। यदि किसी सरणी फ़ील्ड में प्रत्येक तत्व महत्वपूर्ण है, तो आप रिलीज़ 1.5 में जोड़े गए Arrays.hashCode विधियों में से किसी एक का उपयोग कर सकते हैं।


0

शुरुआत में एक बड़ा नक्शा आवंटित करें। यदि आप जानते हैं कि इसमें 26 मिलियन प्रविष्टियां होंगी और आपके पास इसके लिए मेमोरी है, तो ए new HashMap(30000000)

क्या आप सुनिश्चित हैं, आपके पास 26 मिलियन कुंजी और मान के साथ 26 मिलियन प्रविष्टियों के लिए पर्याप्त मेमोरी है? यह मुझे बहुत याद आता है। क्या आप सुनिश्चित हैं कि कचरा संग्रह आपके 2 से 3 मिलियन के निशान पर अभी भी ठीक कर रहा है? मैं एक अड़चन के रूप में कल्पना कर सकता था।


2
ओह, एक और बात। मानचित्र में एकल स्थानों पर बड़ी लिंक से बचने के लिए आपके हैश कोड समान रूप से वितरित किए जाने हैं।
रेनेस

0

आप दो चीजें आज़मा सकते हैं:

  • अपने hashCodeतरीके को कुछ सरल और अधिक प्रभावी बनाएं जैसे कि एक निरंतर इंट

  • अपने नक्शे को इस प्रकार बनाएं:

    Map map = new HashMap( 30000000, .95f );

उन दो कार्यों से संरचना को फिर से करने की मात्रा में काफी कमी आएगी, और मुझे लगता है कि परीक्षण करना बहुत आसान है।

यदि वह काम नहीं करता है, तो एक अलग भंडारण जैसे RDBMS का उपयोग करने पर विचार करें।

संपादित करें

यह अजीब है कि प्रारंभिक क्षमता निर्धारित करने से आपके मामले में प्रदर्शन कम हो जाता है।

Javadocs से देखें :

यदि प्रारंभिक क्षमता लोड फैक्टर द्वारा विभाजित प्रविष्टियों की अधिकतम संख्या से अधिक है, तो कोई भी पुन: संचालन नहीं होगा।

मैंने एक माइक्रोबिचमार्क बनाया (जो किसी भी निश्चित नहीं है लेकिन कम से कम इस बिंदु को साबित करता है)

$cat Huge*java
import java.util.*;
public class Huge {
    public static void main( String [] args ) {
        Map map = new HashMap( 30000000 , 0.95f );
        for( int i = 0 ; i < 26000000 ; i ++ ) { 
            map.put( i, i );
        }
    }
}
import java.util.*;
public class Huge2 {
    public static void main( String [] args ) {
        Map map = new HashMap();
        for( int i = 0 ; i < 26000000 ; i ++ ) { 
            map.put( i, i );
        }
    }
}
$time java -Xms2g -Xmx2g Huge

real    0m16.207s
user    0m14.761s
sys 0m1.377s
$time java -Xms2g -Xmx2g Huge2

real    0m21.781s
user    0m20.045s
sys 0m1.656s
$

तो, प्रारंभिक क्षमता का उपयोग करते हुए 21s से 16s को फिर से जोड़ने के कारण गिरता है। hashCode"अवसर के क्षेत्र" के रूप में हमें अपनी पद्धति से छोड़ें ;)

संपादित करें

हैशमैप नहीं है

अपने अंतिम संस्करण के अनुसार।

मुझे लगता है कि आपको वास्तव में अपने आवेदन को प्रोफाइल करना चाहिए और देखना चाहिए कि यह मेमोरी / सीपीयू कहां खपत हो रही है।

मैंने आपका एक वर्ग लागू किया है hashCode

यह हैश कोड लाखों टकराव देता है, फिर हाशप में प्रविष्टियां नाटकीय रूप से कम हो जाती हैं।

मैं अपने पिछले परीक्षण में 21, 16 वीं से 10 वीं और 8 वीं पास हूं। इसका कारण है कि हैशकोड बड़ी संख्या में टकराव को उकसाता है और आप उन 26M वस्तुओं को संग्रहीत नहीं कर रहे हैं जिन्हें आप सोचते हैं, लेकिन बहुत महत्वपूर्ण संख्या (लगभग 20k मैं कहूंगा) तो:

आपके कोड में कहीं और समस्याएं नहीं हैं

यह एक प्रोफाइलर पाने और यह पता लगाने का समय है। मुझे लगता है कि यह आइटम के निर्माण पर है या शायद आप डिस्क से लिख रहे हैं या नेटवर्क से डेटा प्राप्त कर रहे हैं।

यहाँ मेरी अपनी कक्षा का कार्यान्वयन है।

ध्यान दें कि मैंने 0-51 की श्रेणी का उपयोग नहीं किया था, लेकिन आपने अपने मूल्यों के लिए -126 से लेकर 127 तक दोहराया है, ऐसा इसलिए है क्योंकि आपके प्रश्न को अपडेट करने से पहले मैंने यह परीक्षण किया था

अंतर केवल इतना है कि आपकी कक्षा में अधिक टकराव होंगे और इस प्रकार नक्शे में संग्रहीत कम आइटम होंगे।

import java.util.*;
public class Item {

    private static byte w = Byte.MIN_VALUE;
    private static byte x = Byte.MIN_VALUE;
    private static byte y = Byte.MIN_VALUE;
    private static byte z = Byte.MIN_VALUE;

    // Just to avoid typing :) 
    private static final byte M = Byte.MAX_VALUE;
    private static final byte m = Byte.MIN_VALUE;


    private byte [] a = new byte[2];
    private byte [] b = new byte[3];

    public Item () {
        // make a different value for the bytes
        increment();
        a[0] = z;        a[1] = y;    
        b[0] = x;        b[1] = w;   b[2] = z;
    }

    private static void increment() {
        z++;
        if( z == M ) {
            z = m;
            y++;
        }
        if( y == M ) {
            y = m;
            x++;
        }
        if( x == M ) {
            x = m;
            w++;
        }
    }
    public String toString() {
        return "" + this.hashCode();
    }



    public int hashCode() {
        int hash = 503;
        hash = hash * 5381 + (a[0] + a[1]);
        hash = hash * 5381 + (b[0] + b[1] + b[2]);
        return hash;
    }
    // I don't realy care about this right now. 
    public boolean equals( Object other ) {
        return this.hashCode() == other.hashCode();
    }

    // print how many collisions do we have in 26M items.
    public static void main( String [] args ) {
        Set set = new HashSet();
        int collisions = 0;
        for ( int i = 0 ; i < 26000000 ; i++ ) {
            if( ! set.add( new Item() ) ) {
                collisions++;
            }
        }
        System.out.println( collisions );
    }
}

इस वर्ग का उपयोग करने के लिए पिछले कार्यक्रम की कुंजी है

 map.put( new Item() , i );

मुझे देता है:

real     0m11.188s
user     0m10.784s
sys 0m0.261s


real     0m9.348s
user     0m9.071s
sys  0m0.161s

3
ऑस्कर, जैसा कि ऊपर कहीं और बताया गया है (आपकी टिप्पणियों के जवाब में), आपको लगता है कि अधिक टकराव अच्छा है; यह बहुत अच्छा नहीं है। एक टकराव का मतलब है कि किसी दिए गए हैश में स्लॉट एकल प्रविष्टि से युक्त होता है जिसमें प्रविष्टियों की एक सूची होती है, और इस सूची को स्लॉट के एक्सेस होने पर हर बार खोजा / ट्रैवर्स किया जाना होता है।
21

@ डेडल्यूगो: वास्तव में, यह तब ही होता है जब आपके पास विभिन्न वर्गों का उपयोग करके टकराव होता है, लेकिन एक ही कक्षा के लिए एक ही प्रविष्टि का उपयोग किया जाता है;)
ऑस्कररेज़

2
@ ऑस्कर - मेक के जवाब के साथ मेरी प्रतिक्रिया देखें। HashMap प्रत्येक हैश बाल्टी में प्रविष्टियों की एक लिंक की गई सूची को बनाए रखता है, और प्रत्येक तत्व पर उस सूची को बराबर () कहता है। ऑब्जेक्ट की कक्षा का इससे कोई लेना-देना नहीं है (बराबरी पर शॉर्ट-सर्किट के अलावा) ()।
kdgregory

1
@ ऑस्कर - आपके उत्तर को पढ़कर ऐसा लगता है कि आप मान रहे हैं कि अगर हैशकोड समान हैं तो () सही होगा। यह बराबरी / हैशकोड अनुबंध का हिस्सा नहीं है। अगर मैंने गलत समझा है, तो इस टिप्पणी को अनदेखा करें।
kdgregory

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


0

मैंने कुछ समय पहले एक सूची बनाम हैशमैप के साथ एक छोटा सा परीक्षण किया था, मजेदार बात सूची के माध्यम से परेशान कर रही थी और ऑब्जेक्ट को मिलीसेकंड में समान समय लगा था क्योंकि हैशमैप्स फंक्शन का उपयोग करके ... बस एक फी। ओह हाँ मेमोरी एक बड़ा मुद्दा है जब उस आकार के हैशैप्स के साथ काम करना।


0

उपयोग की जाने वाली लोकप्रिय हैशिंग विधियाँ वास्तव में बड़े सेटों के लिए बहुत अच्छी नहीं हैं और, जैसा कि ऊपर बताया गया है, उपयोग की गई हैश विशेष रूप से खराब है। बेहतर उच्च मिश्रण और कवरेज के साथ हैश एल्गोरिथ्म का उपयोग करने के लिए है जैसे कि बुज़हश ( http://www.java2s.com/Code/Java/Development-Class/Averyefficientjavahashal एल्गोरिथम्मॉडेजडॉटहॉज़हॉशलगिटम .htm पर नमूना कार्यान्वयन )

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