स्ट्रिंग में जावा का हैशकोड () 31 क्यों एक गुणक के रूप में उपयोग करता है?


480

जावा प्रलेखन के अनुसार, किसी वस्तु के लिए हैश कोडString की गणना इस प्रकार की जाती है:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

का उपयोग करते हुए intगणित, जहां s[i]है मैं , तार का वें चरित्र nस्ट्रिंग की लंबाई है, और ^घातांक इंगित करता है।

31 को गुणक के रूप में क्यों प्रयोग किया जाता है?

मैं समझता हूं कि गुणक अपेक्षाकृत बड़ी अभाज्य संख्या होनी चाहिए। तो 29, या 37, या 97 भी क्यों नहीं?


1
भी की तुलना करें stackoverflow.com/questions/1835976/... - मुझे लगता है कि 31 एक बुरा विकल्प है यदि आप अपने खुद के hashCode कार्यों लिखना है।
हंस-पीटर स्टॉर

6
अगर यह 29, या 37, या 97 भी था, तो आप पूछेंगे 'क्यों 31 नहीं?'
लोर्ने

2
@ ईजेपी के लिए नो के चुनाव के पीछे का कारण जानना जरूरी है। जब तक संख्या एक काले जादू की चाल का परिणाम नहीं है।
दुष्यंत सभरवाल 13

इसके बारे में @ peter-lawrey द्वारा एक ब्लॉग पोस्ट यहाँ है: vanilla-java.github.io/2018/08/12/… और यहाँ: vanilla-java.github.io/2018/08/15/…
Roussy

@DushyantSabharwal मेरे मुद्दा यह है कि यह कर सकता है किया गया 29 या 37 या 97, या 41, या कई अन्य मूल्यों, बहुत व्यावहारिक अंतर नहीं करने पड़ते। हम १ ९ were६ में ३ 1976 का उपयोग कर रहे थे।
मार्स

जवाबों:


405

यहोशू बलोच के प्रभावी जावा के अनुसार (एक पुस्तक जिसे पर्याप्त रूप से अनुशंसित नहीं किया जा सकता है, और जिसे मैंने स्टैकवर्कफ़्लो पर नित्य उल्लेखों के लिए खरीदा है):

मान 31 को चुना गया क्योंकि यह एक अजीब प्राइम है। यदि यह और भी गुणा किया गया था, तो जानकारी खो जाएगी, क्योंकि 2 से गुणा करना स्थानांतरण के बराबर है। प्राइम का उपयोग करने का लाभ कम स्पष्ट है, लेकिन यह पारंपरिक है। 31 की एक अच्छी संपत्ति यह है कि गुणा को एक बदलाव और बेहतर प्रदर्शन के लिए घटाव द्वारा प्रतिस्थापित किया जा सकता है 31 * i == (i << 5) - i:। आधुनिक VM इस प्रकार का अनुकूलन स्वचालित रूप से करते हैं।

(अध्याय 3, आइटम 9 से: हमेशा हैशकोड को ओवरराइड करें जब आप बराबर हो जाएं, पृष्ठ 48)


346
अच्छी तरह से सभी primes विषम हैं, सिवाय 2. बस कह रहे हैं।
किप

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

50
31 चुना गया था coz यह एक अजीब प्रधानमंत्री है ??? यह does not कोई मतलब - मैं कहता हूँ, क्योंकि यह सबसे अच्छा वितरण दिया 31 में चुना गया था - जांच computinglife.wordpress.com/2008/11/20/...
computinglife

65
मुझे लगता है कि 31 का चुनाव दुर्भाग्यपूर्ण है। निश्चित रूप से, यह पुरानी मशीनों पर कुछ सीपीयू चक्रों को बचा सकता है, लेकिन आपके पास "@ और #, या सीए और डीबी जैसी छोटी एससीआई स्ट्रिंग्स पर पहले से ही हैश टकराव हैं। उदाहरण के लिए, 1327144003, या पर यह नहीं होता है। कम से कम 524287 जो बिटशिफ्ट की भी अनुमति देता है: 524287 * i == i << 19 - i।
हंस-पीटर स्टॉर

15
@ जेसन मेरा उत्तर देखें stackoverflow.com/questions/1835976/… । मेरा कहना है: यदि आप बड़े प्राइम का उपयोग करते हैं, तो आप बहुत कम टकराव प्राप्त करते हैं, और इन दिनों कुछ भी नहीं खोते हैं। यदि आप सामान्य गैर-असिसी वर्ण वाली गैर-अंग्रेजी भाषाओं का उपयोग करते हैं तो समस्या और भी बदतर है। और 31 ने अपने स्वयं के हैशकोड कार्यों को लिखते समय कई प्रोग्रामर के लिए एक खराब उदाहरण के रूप में कार्य किया।
हंस-पीटर स्टॉर

80

जैसा कि गुडरिक और तामसिया बताते हैं, यदि आप ५०,००० से अधिक अंग्रेजी शब्दों (यूनिक्स के दो वेरिएंट में उपलब्ध कराई गई शब्द सूचियों के मिलन के रूप में) का उपयोग करते हैं, तो ३१, ३३, ३ 39, ३ ९, और ४१ का उपयोग कर isions टक्करों से कम उत्पादन होगा प्रत्येक मामले में। यह जानकर, इसमें कोई आश्चर्य नहीं होना चाहिए कि कई जावा कार्यान्वयन इन स्थिरांक में से एक का चयन करते हैं।

संयोग से, मैं इस प्रश्न को देखने के दौरान खंड "बहुपद हैश कोड" को पढ़ने के बीच में था।

EDIT: यहाँ ~ 10mb PDF बुक का लिंक दिया गया है जिसका मैं ऊपर उल्लेख कर रहा हूँ। जावा में डेटा स्ट्रक्चर्स और अल्गोरिदम के सेक्शन 10.2 हैश टेबल (पेज 413) देखें


6
ध्यान दें कि यदि आप ASCII सीमा के बाहर आम वर्णों के साथ किसी भी प्रकार के अंतर्राष्ट्रीय चारसेट का उपयोग करते हैं, तो आपको अधिक टक्कर मिल सकती है। कम से कम, मैंने इसे 31 और जर्मन के लिए चेक किया। इसलिए मुझे लगता है कि 31 का चुनाव टूट गया है।
हंस-पीटर स्टॉर

1
@jJack, आपके उत्तर में दी गई लिंक टूट गई है।
SK वेंकट

इस उत्तर के दोनों लिंक टूटे हुए हैं। इसके अलावा, पहले पैराग्राफ में तर्क अधूरा है; आप इस बेंचमार्क पर सूचीबद्ध पांच के साथ अन्य विषम संख्याओं की तुलना कैसे करते हैं?
मार्क अमेरी

58

(ज्यादातर) पुराने प्रोसेसर पर, 31 से गुणा करना अपेक्षाकृत सस्ता हो सकता है। उदाहरण के लिए, ARM पर, यह केवल एक निर्देश है:

RSB       r1, r0, r0, ASL #5    ; r1 := - r0 + (r0<<5)

अधिकांश अन्य प्रोसेसर को एक अलग शिफ्ट की आवश्यकता होती है और निर्देश को घटाना होता है। हालांकि, अगर आपका गुणक धीमा है, तो यह अभी भी एक जीत है। आधुनिक प्रोसेसर में तेज गुणक होते हैं, इसलिए इससे अधिक अंतर नहीं पड़ता है, इसलिए जब तक 32 सही पक्ष पर नहीं जाता है।

यह एक महान हैश एल्गोरिथ्म नहीं है, लेकिन यह 1.0 कोड की तुलना में काफी अच्छा और बेहतर है (और 1.0 से बेहतर है)।


7
मजेदार बात यह है कि 31 के साथ गुणा मेरे डेस्कटॉप मशीन पर है, वास्तव में, 92821 के साथ गुणा की तुलना में थोड़ा धीमा है। मुझे लगता है कि कंपाइलर इसे शिफ्ट में "ऑप्टिमाइज़" करने और साथ ही जोड़ने की कोशिश करता है। :-)
हंस-पीटर स्टॉर

1
मुझे नहीं लगता कि मैंने कभी एक एआरएम का उपयोग किया है जो सीमा +/- 255 में सभी मूल्यों के साथ समान रूप से तेज़ नहीं था। 2 शून्य से एक की शक्ति का उपयोग करने का दुर्भाग्यपूर्ण प्रभाव है कि दो मूल्यों के मिलान से दो की शक्ति से हैश कोड बदल जाता है। -31 का मान बेहतर होता, और मुझे लगता है कि ऐसा कुछ होगा -83 (64 + 16 + 2 + 1) अभी तक बेहतर रहा होगा (बिट्स को कुछ बेहतर तरीके से ब्लेंड करना)।
सुपरकैट

@supercat माइनस से आश्वस्त नहीं। लगता है कि आप शून्य की ओर वापस जा रहे हैं। / String.hashCodeस्ट्रॉन्गार्म को पूर्ववर्ती करता है, जिसे IIRC ने 8-बिट गुणक की शुरुआत की और संभवत: शिफ्ट ऑपरेशन के साथ संयुक्त अंकगणित / तार्किक के लिए दो चक्रों तक बढ़ा दिया।
टॉम हॉकिन -

1
@ टॉमहॉटिन-टैक्लाइन: 31 का उपयोग करते हुए, चार मानों का हैश 29791 * a + 961 * b + 31 * c + d होगा; -31 का उपयोग करते हुए, यह -29791 * a + 961 * b - 31 * c + d होगा। मुझे नहीं लगता कि चार वस्तुओं के स्वतंत्र होने पर अंतर महत्वपूर्ण होगा, लेकिन अगर आसन्न वस्तुओं के जोड़े मेल खाते हैं, तो परिणामी हैश कोड सभी अप्रकाशित वस्तुओं का योगदान होगा, साथ ही कुछ 32 (जोड़े में से) के कई गुण होंगे। तार के लिए यह बहुत ज्यादा मायने नहीं रखता है, लेकिन अगर कोई हैशिंग एकत्रीकरण के लिए एक सामान्य-उद्देश्य विधि लिख रहा है, तो उस स्थिति में जहां आसन्न आइटम मैच बिल्कुल सामान्य होगा।
सुपरकैट

3
@supercat मजेदार तथ्य, हैश कोड Map.Entryविनिर्देश द्वारा तय किया गया है key.hashCode() ^ value.hashCode()इसके बावजूद कि यह एक अनियंत्रित जोड़ी भी नहीं है, जैसा कि keyऔर valueपूरी तरह से अलग अर्थ रखता है। हां, इसका तात्पर्य है कि Map.of(42, 42).hashCode()या Map.of("foo", "foo", "bar", "bar").hashCode(), आदि, अनुमानित रूप से शून्य हैं। अतः मानचित्रों को अन्य मानचित्रों की कुंजी के रूप में उपयोग न करें ...
Holger

33

गुणा करके, बिट्स को बाईं ओर स्थानांतरित कर दिया जाता है। यह हैश कोड के उपलब्ध स्थान का अधिक उपयोग करता है, जिससे टकराव कम होता है।

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

अभिव्यक्ति n * 31के बराबर है (n << 5) - n


29

आप http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622 में "टिप्पणियां" के तहत बलोच के मूल तर्क को पढ़ सकते हैं । उन्होंने हैश तालिका में परिणामी "औसत श्रृंखला आकार" के संबंध में विभिन्न हैश कार्यों के प्रदर्शन की जांच की। P(31)उस समय के आम कार्यों में से एक था जो उन्होंने के एंड आर की पुस्तक में पाया था (लेकिन यहां तक ​​कि कर्निघन और रिची याद नहीं कर सके कि यह कहां से आया है)। अंत में उन्हें मूल रूप से एक का चयन करना था और इसलिए उन्होंने लिया P(31)क्योंकि यह काफी अच्छा प्रदर्शन करने के लिए लग रहा था। भले ही P(33)वास्तव में बदतर नहीं था और 33 से गुणा करना गणना करने के लिए समान रूप से तेज है (सिर्फ एक बदलाव 5 और एक अतिरिक्त), उसने 31 के लिए चुना क्योंकि 33 एक प्रमुख नहीं है:

शेष चार में से, मैं शायद P (31) का चयन करूंगा, क्योंकि यह RISC मशीन पर गणना करने के लिए सबसे सस्ता है (क्योंकि 31 दो की दो शक्तियों का अंतर है)। P (33) गणना करने के लिए समान रूप से सस्ता है, लेकिन यह प्रदर्शन थोड़ा खराब है, और 33 समग्र है, जो मुझे थोड़ा परेशान करता है।

इसलिए तर्क उतना तर्कसंगत नहीं था, क्योंकि यहाँ बहुत से उत्तर स्पष्ट रूप से दिए गए हैं। लेकिन हम सभी आंतकी फैसलों के बाद तर्कसंगत कारणों के साथ आने में अच्छे हैं (और बलोच को भी इसका खतरा हो सकता है)।


2
एक गहन शोध और निष्पक्ष उत्तर!
विशाल के

22

वास्तव में, 37 बहुत अच्छी तरह से काम करेगा! z: = 37 * x की गणना की जा सकती है y := x + 8 * x; z := x + 4 * y। दोनों चरण एक LEA x86 निर्देशों के अनुरूप हैं, इसलिए यह बहुत तेज़ है।

वास्तव में, यहां तक कि बड़ा प्रधानमंत्री के साथ गुणा 73 सेटिंग से एक ही गति से किया जा सकता है y := x + 8 * x; z := x + 8 * y

73 (37 (31 के बजाय) का उपयोग करना बेहतर हो सकता है, क्योंकि यह सघन कोड की ओर जाता है : दो एलईए निर्देश केवल 6 बाइट्स को 7 बाइट्स में ले जाते हैं + 31 + को गुणा करने के लिए स्थानांतरित + घटाना 31 से गुणा करते हैं। एक संभावित कैवेट वह है 3-तर्क LEA निर्देश का उपयोग यहां इंटेल के सैंडी पुल वास्तुकला पर धीमा हो गया, जिसमें 3 चक्रों की वृद्धि हुई विलंबता थी।

इसके अलावा, 73 शेल्डन कूपर की पसंदीदा संख्या है।


5
आप पास्कल प्रोग्रामर हैं या कुछ और? क्या साथ है: = सामान?
मिंगुय

11
@Mainguy यह वास्तव में ALGOL सिंटैक्स है और छद्म कोड में काफी बार उपयोग किया जाता है।
एप्रोचिंगडार्कनेस

4
लेकिन एआरएम असेंबली गुणा में एक एकल निर्देश में किया जा सकता है
phuclv


में TPOP (1999) एक जल्दी जावा (p.57) के बारे में पढ़ सकते हैं: "... समस्या एक हम (के गुणक के साथ दिखाया गया है करने के लिए एक बराबर के साथ हैश की जगह द्वारा हल किया गया 37 ) ..."
मिकू

19

नील कॉफ़ी बताते हैं कि पूर्वाग्रह से लोहा लेने के लिए 31 का इस्तेमाल क्यों किया जाता है ।

मूल रूप से 31 का उपयोग करने से आपको हैश फ़ंक्शन के लिए और भी अधिक बिट-बिट संभावना वितरण मिलता है।


12

से JDK-4045622 , जहां यहोशू बलोच कारण है कि विशेष (नया) का वर्णन करता है String.hashCode()कार्यान्वयन में चुना गया था

नीचे दी गई तालिका तीन डेटा सेटों के लिए ऊपर वर्णित विभिन्न हैश कार्यों के प्रदर्शन को सारांशित करती है:

1) मेरियम-वेबस्टर के 2 इन्टल अनब्रिडेड डिक्शनरी (311,141 तार, एवीजी लंबाई 10 वर्ण) में प्रविष्टियों के साथ सभी शब्द और वाक्यांश।

2) में / बिन तार के सभी / , / usr / bin / , / usr / lib / , / usr / यूसीबी / और / usr / openwin / bin / * (66,304 तार, औसत लंबाई 21 वर्ण)।

3) एक वेब-क्रॉलर द्वारा एकत्र किए गए URL की एक सूची जो कल रात कई घंटों तक चली (28,372 तार, औसत लंबाई 49 अक्षर)।

तालिका में दिखाया गया प्रदर्शन मीट्रिक हैश तालिका में सभी तत्वों के ऊपर "औसत श्रृंखला का आकार" है (यानी, एक तत्व को देखने के लिए कुंजी की संख्या का अपेक्षित मूल्य)।

                          Webster's   Code Strings    URLs
                          ---------   ------------    ----
Current Java Fn.          1.2509      1.2738          13.2560
P(37)    [Java]           1.2508      1.2481          1.2454
P(65599) [Aho et al]      1.2490      1.2510          1.2450
P(31)    [K+R]            1.2500      1.2488          1.2425
P(33)    [Torek]          1.2500      1.2500          1.2453
Vo's Fn                   1.2487      1.2471          1.2462
WAIS Fn                   1.2497      1.2519          1.2452
Weinberger's Fn(MatPak)   6.5169      7.2142          30.6864
Weinberger's Fn(24)       1.3222      1.2791          1.9732
Weinberger's Fn(28)       1.2530      1.2506          1.2439

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

मैं WAIS फ़ंक्शन को समाप्त कर दूंगा क्योंकि इसके विनिर्देश में यादृच्छिक संख्याओं के पृष्ठ शामिल हैं, और इसका प्रदर्शन किसी भी सरल कार्य से बेहतर नहीं है। शेष छह कार्यों में से कोई भी उत्कृष्ट विकल्प लगता है, लेकिन हमें एक चुनना होगा। मुझे लगता है कि मैं उनके अतिरिक्त जटिलता के कारण Vo के वैरिएंट और वेनबर्गर के कार्य को नियंत्रित करूंगा। शेष चार में से, मैं शायद P (31) का चयन करूंगा, क्योंकि यह RISC मशीन पर गणना करने के लिए सबसे सस्ता है (क्योंकि 31 दो की दो शक्तियों का अंतर है)। P (33) गणना करने के लिए समान रूप से सस्ता है, लेकिन यह प्रदर्शन थोड़ा खराब है, और 33 समग्र है, जो मुझे थोड़ा परेशान करता है।

जोश


5

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

हैश का उपयोग करने वाले नंबर आम तौर पर होते हैं:

  • आपके द्वारा टाइप किए गए डेटा प्रकार के मापांक (2 ^ 32 या 2 ^ 64)
  • आपके हैशटेबल में बकेट काउंट का मापांक (भिन्न होता है। जावा में प्राइम होता था, अब 2 ^ n)
  • अपने मिक्सिंग फंक्शन में एक मैजिक नंबर से गुणा या शिफ्ट करें
  • इनपुट मूल्य

आपको वास्तव में केवल इन मूल्यों के एक जोड़े को नियंत्रित करने के लिए मिलता है, इसलिए थोड़ी अतिरिक्त देखभाल के कारण है।


4

JDK के नवीनतम संस्करण में, अभी भी 31 का उपयोग किया जाता है। https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/String.html#hashCode ()

हैश स्ट्रिंग का उद्देश्य है

  • अद्वितीय ( ^हैशकोड गणना दस्तावेज़ में ऑपरेटर देखें , यह अद्वितीय मदद करता है)
  • गणना के लिए सस्ती लागत

31 अधिकतम मूल्य 8 बिट (= 1 बाइट) रजिस्टर में डाला जा सकता है, सबसे बड़ा अभाज्य नंबर 1 बाइट रजिस्टर में रखा जा सकता है, विषम संख्या है।

गुणक 31 है << 5 तो अपने आप को घटाएं, इसलिए सस्ते संसाधनों की आवश्यकता है।


3

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


1

ऐसा इसलिए है क्योंकि 31 के पास एक अच्छी संपत्ति है - इसका गुणन एक बिटवाइज़ शिफ्ट द्वारा प्रतिस्थापित किया जा सकता है जो मानक गुणन की तुलना में तेज़ है:

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