हम hashCode()
एक संग्रह के लिए विधि के सर्वोत्तम कार्यान्वयन पर कैसे निर्णय लेते हैं (यह मानते हुए कि विधि समान रूप से ओवरराइड की गई है)?
collection.hashCode()
( hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96d96a0f/src/share/… )
हम hashCode()
एक संग्रह के लिए विधि के सर्वोत्तम कार्यान्वयन पर कैसे निर्णय लेते हैं (यह मानते हुए कि विधि समान रूप से ओवरराइड की गई है)?
collection.hashCode()
( hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96d96a0f/src/share/… )
जवाबों:
सबसे अच्छा कार्यान्वयन? यह एक कठिन प्रश्न है क्योंकि यह उपयोग पैटर्न पर निर्भर करता है।
जोश बलोच के प्रभावी जावा को आइटम 8 (दूसरे संस्करण) में लगभग सभी मामलों के लिए उचित अच्छा कार्यान्वयन प्रस्तावित किया गया था । सबसे अच्छी बात यह है कि इसे वहाँ देखना है क्योंकि लेखक वहाँ बताता है कि दृष्टिकोण अच्छा क्यों है।
एक बनाएँ int result
और एक गैर-शून्य मान असाइन करें ।
विधि में परीक्षण किए गए प्रत्येक क्षेत्र के लिए , एक हैश कोड की गणना करें :f
equals()
c
boolean
: गणना (f ? 0 : 1)
;byte
, char
, short
या int
: calculate (int)f
;long
: गणना (int)(f ^ (f >>> 32))
;float
: गणना Float.floatToIntBits(f)
;double
: Double.doubleToLongBits(f)
हर लंबे मूल्य की तरह वापसी मूल्य की गणना और संभाल;hashCode()
विधि के परिणाम का उपयोग करें या यदि 0 f == null
;के c
साथ हैश मान को मिलाएं result
:
result = 37 * result + c
वापसी result
इसके परिणामस्वरूप अधिकांश उपयोग स्थितियों के लिए हैश मानों का उचित वितरण होना चाहिए।
यदि आप dmeister द्वारा सुझाए गए प्रभावी जावा कार्यान्वयन से खुश हैं, तो आप अपना स्वयं का रोल करने के बजाय लाइब्रेरी कॉल का उपयोग कर सकते हैं:
@Override
public int hashCode() {
return Objects.hashCode(this.firstName, this.lastName);
}
इसके लिए com.google.common.base.Objects.hashCode
जावा 7 में या तो अमरूद ( ) या मानक पुस्तकालय की आवश्यकता होती है, java.util.Objects.hash
लेकिन उसी तरह काम करता है।
hashCode
, यदि आपके पास कोई रिवाज है equals
, और यह वही है जो इन लाइब्रेरी विधियों के लिए डिज़ाइन किया गया है। प्रलेखन उनके संबंध में उनके व्यवहार पर काफी स्पष्ट है equals
। एक पुस्तकालय कार्यान्वयन आपको यह जानने से वंचित करने का दावा नहीं करता है कि एक सही hashCode
क्रियान्वयन की विशेषताएं क्या हैं - ये पुस्तकालय आपके लिए उन मामलों के बहुमत के लिए इस तरह के अनुरूप कार्यान्वयन को लागू करना आसान बनाते हैं जहां equals
यह अधिक महत्वपूर्ण है।
java.util.Objects.hash(...)
अमरूद com.google.common.base.Objects.hashCode(...)
विधि के बजाय JDK7 विधि को चुना होगा । मुझे लगता है कि ज्यादातर लोग एक अतिरिक्त निर्भरता पर मानक पुस्तकालय का चयन करेंगे।
hashCode()
एक सरणी के लिए यह बस है java.lang.System.identityHashCode(...)
।
एक्लिप्स द्वारा प्रदान की गई कार्यक्षमता का उपयोग करना बेहतर है जो एक बहुत अच्छा काम करता है और आप अपने तर्क और ऊर्जा को व्यापार तर्क विकसित करने में लगा सकते हैं।
हालाँकि यह Android
दस्तावेज़ीकरण (वेबैक मशीन) और मेरा खुद का कोड गितुब से जुड़ा है , यह सामान्य रूप से जावा के लिए काम करेगा। मेरा जवाब सिर्फ कोड के साथ डमीस्टर के उत्तर का विस्तार है जो पढ़ने और समझने में बहुत आसान है।
@Override
public int hashCode() {
// Start with a non-zero constant. Prime is preferred
int result = 17;
// Include a hash for each field.
// Primatives
result = 31 * result + (booleanField ? 1 : 0); // 1 bit » 32-bit
result = 31 * result + byteField; // 8 bits » 32-bit
result = 31 * result + charField; // 16 bits » 32-bit
result = 31 * result + shortField; // 16 bits » 32-bit
result = 31 * result + intField; // 32 bits » 32-bit
result = 31 * result + (int)(longField ^ (longField >>> 32)); // 64 bits » 32-bit
result = 31 * result + Float.floatToIntBits(floatField); // 32 bits » 32-bit
long doubleFieldBits = Double.doubleToLongBits(doubleField); // 64 bits (double) » 64-bit (long) » 32-bit (int)
result = 31 * result + (int)(doubleFieldBits ^ (doubleFieldBits >>> 32));
// Objects
result = 31 * result + Arrays.hashCode(arrayField); // var bits » 32-bit
result = 31 * result + referenceField.hashCode(); // var bits » 32-bit (non-nullable)
result = 31 * result + // var bits » 32-bit (nullable)
(nullableReferenceField == null
? 0
: nullableReferenceField.hashCode());
return result;
}
संपादित करें
आमतौर पर, जब आप ओवरराइड करते हैं, तो आप ओवरराइड hashcode(...)
भी करना चाहते हैं equals(...)
। तो उन लोगों के लिए जो पहले से ही लागू हैं या लागू किए गए हैं equals
, यहां मेरे गिठुब से एक अच्छा संदर्भ है ...
@Override
public boolean equals(Object o) {
// Optimization (not required).
if (this == o) {
return true;
}
// Return false if the other object has the wrong type, interface, or is null.
if (!(o instanceof MyType)) {
return false;
}
MyType lhs = (MyType) o; // lhs means "left hand side"
// Primitive fields
return booleanField == lhs.booleanField
&& byteField == lhs.byteField
&& charField == lhs.charField
&& shortField == lhs.shortField
&& intField == lhs.intField
&& longField == lhs.longField
&& floatField == lhs.floatField
&& doubleField == lhs.doubleField
// Arrays
&& Arrays.equals(arrayField, lhs.arrayField)
// Objects
&& referenceField.equals(lhs.referenceField)
&& (nullableReferenceField == null
? lhs.nullableReferenceField == null
: nullableReferenceField.equals(lhs.nullableReferenceField));
}
पहले सुनिश्चित करें कि समान रूप से सही तरीके से लागू किया गया है। से एक आईबीएम डेवलपर लेख :
- समरूपता: दो संदर्भों के लिए, a और b, a .equals (b) यदि और केवल यदि b.equals (a)
- संवेदनशीलता: सभी गैर-शून्य संदर्भों के लिए, a.equals (a)
- परिवर्तनशीलता: यदि a.equals (b) और b.equals (c), तो a.equals (c)
फिर सुनिश्चित करें कि हैशकोड के साथ उनके संबंध संपर्क का सम्मान करते हैं (उसी लेख से):
- हैशकोड () के साथ संगति: दो समान वस्तुओं में समान हैशकोड () मान होना चाहिए
अंत में एक अच्छा हैश फ़ंक्शन आदर्श हैश फ़ंक्शन को देखने का प्रयास करना चाहिए ।
about8.blogspot.com, आपने कहा
यदि बराबर () दो वस्तुओं के लिए सही है, तो हैशकोड () को समान मान लौटना चाहिए। यदि बराबर () गलत है, तो हैशकोड () अलग-अलग मान वापस करना चाहिए
मैं आपसे सहमत नहीं हो सकता। यदि दो वस्तुओं में समान हैशकोड होता है तो इसका मतलब यह नहीं है कि वे समान हैं।
यदि A, B के बराबर है, तो A.Ashcode B.hascode के बराबर होना चाहिए
परंतु
यदि A.hashcode B.hascode के बराबर है तो इसका मतलब यह नहीं है कि A को B के बराबर होना चाहिए
(A != B) and (A.hashcode() == B.hashcode())
, इसे हम हैश फंक्शन की टक्कर कहते हैं। ऐसा इसलिए है क्योंकि हैश फ़ंक्शन का कोडोमैन हमेशा परिमित रहता है, जबकि यह डोमेन आमतौर पर नहीं होता है। कोडोमैन जितना बड़ा होता है, उतनी बार टकराव कम होना चाहिए। अच्छे हैश फंक्शन को अलग-अलग ऑब्जेक्ट्स के लिए अलग-अलग हैश को लौटना चाहिए, जिसमें सबसे बड़ी संभावना प्राप्त की गई विशेष कोडोमेन साइज है। यह शायद ही कभी पूरी तरह से गारंटी दी जा सकती है।
यदि आप ग्रहण का उपयोग करते हैं, तो आप उत्पन्न equals()
और hashCode()
उपयोग कर सकते हैं :
स्रोत -> हैशकोड उत्पन्न करें () और बराबर ()।
इस फ़ंक्शन का उपयोग करके आप यह तय कर सकते हैं कि आप किन क्षेत्रों में समानता और हैश कोड गणना के लिए उपयोग करना चाहते हैं, और एक्लिप्स इसी तरीके को उत्पन्न करता है।
Apache Commons Lang में प्रभावी Java के तर्क hashcode()
और equals()
तर्क का अच्छा कार्यान्वयन है । चेकआउट हैशकोडबाउटल और इक्वल्सबाली ।
Objects
वर्ग Java7 पर से प्रदान करता है hash(Object ..args)
और equals()
तरीके। इन्हें jdk 1.7+
IdentityHashMap
) को कम करना। FWIW I एक आईडी-आधारित हैशकोड का उपयोग करता है और सभी संस्थाओं के लिए बराबर है।
अन्य अधिक विस्तृत उत्तर (कोड की अवधि में) को पूरा करने के लिए बस एक त्वरित नोट:
यदि मैं इस प्रश्न पर विचार करता हूं कि कैसे-कैसे-मैं-एक-हैश-टेबल-इन-जावा और विशेष रूप से jGuru FAQ प्रविष्टि , तो मुझे विश्वास है कि कुछ अन्य मानदंड हैं जिन पर एक हैश कोड का न्याय किया जा सकता है:
यदि मैं आपके प्रश्न को सही ढंग से समझता हूं, तो आपके पास एक कस्टम कलेक्शन क्लास है (यानी एक नया वर्ग जो संग्रह इंटरफ़ेस से निकलता है) और आप हैशकोड () पद्धति को लागू करना चाहते हैं।
यदि आपका संग्रह वर्ग AbstractList को बढ़ाता है, तो आपको इसके बारे में चिंता करने की ज़रूरत नहीं है, पहले से ही समान () और हैशकोड () का कार्यान्वयन है जो सभी ऑब्जेक्ट्स के माध्यम से पुनरावृत्ति करके और उनके हैशकोड () को एक साथ जोड़कर काम करता है।
public int hashCode() {
int hashCode = 1;
Iterator i = iterator();
while (i.hasNext()) {
Object obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
}
अब यदि आप जो चाहते हैं, वह विशिष्ट वर्ग के लिए हैश कोड की गणना करने का सबसे अच्छा तरीका है, तो मैं आम तौर पर सभी फ़ील्ड्स को संसाधित करने के लिए ^ (बिटवाइड एक्सक्लूसिव या) ऑपरेटर का उपयोग करता हूं जो कि बराबर पद्धति में उपयोग करते हैं:
public int hashCode(){
return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}
@ about8: वहाँ एक बहुत गंभीर बग है।
Zam obj1 = new Zam("foo", "bar", "baz");
Zam obj2 = new Zam("fo", "obar", "baz");
वही हैशकोड
आप शायद ऐसा कुछ चाहते हैं
public int hashCode() {
return (getFoo().hashCode() + getBar().hashCode()).toString().hashCode();
(क्या आप इन दिनों सीधे जावा में int से हैशकोड प्राप्त कर सकते हैं? मुझे लगता है कि यह कुछ आटोकास्टिंग करता है .. अगर ऐसा है, तो स्ट्रैस्ट्रिंग को छोड़ें, यह बदसूरत है।)
foo
और bar
उसी की ओर जाता है hashCode
। आपका toString
AFAIK संकलन नहीं करता है, और यदि ऐसा होता है, तो यह बहुत ही अयोग्य है। कुछ ऐसा है जो 109 * getFoo().hashCode() + 57 * getBar().hashCode()
तेज, सरल है और कोई अनावश्यक टकराव पैदा नहीं करता है।
Apache Commons EqualsBuilder और HashCodeBuilder पर प्रतिबिंब विधियों का उपयोग करें ।
मैं एक छोटे रैपर का उपयोग करता हूं Arrays.deepHashCode(...)
क्योंकि यह पैरामीटर के रूप में आपूर्ति की गई सरणियों को सही ढंग से संभालता है
public static int hash(final Object... objects) {
return Arrays.deepHashCode(objects);
}
किसी भी हैशिंग विधि जो समान सीमा पर हैश मान को समान रूप से वितरित करती है एक अच्छा कार्यान्वयन है। प्रभावी जावा ( http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+java&pg=PP1&ots=UZMZ2siN25&&ig=kR0n73DHJOn-D77qGj0wOxAxiZw&hl=hi&hl=hi ) देखें। हैशकोड कार्यान्वयन के लिए वहाँ (आइटम 9 मुझे लगता है ...)।
यहाँ एक और JDK 1.7+ अप्रोच लॉजिक्स के साथ एप्रोच प्रदर्शन है। मैं इसे ऑब्जेक्ट क्लास हैशकोड () हिसाब, शुद्ध JDK निर्भरता और कोई अतिरिक्त मैनुअल काम के साथ बहुत आश्वस्त के रूप में देखता हूं। कृपया ध्यान दें Objects.hash()
अशक्त सहिष्णु है।
मैंने किसी भी equals()
कार्यान्वयन को शामिल नहीं किया है लेकिन वास्तव में आपको इसकी आवश्यकता होगी।
import java.util.Objects;
public class Demo {
public static class A {
private final String param1;
public A(final String param1) {
this.param1 = param1;
}
@Override
public int hashCode() {
return Objects.hash(
super.hashCode(),
this.param1);
}
}
public static class B extends A {
private final String param2;
private final String param3;
public B(
final String param1,
final String param2,
final String param3) {
super(param1);
this.param2 = param2;
this.param3 = param3;
}
@Override
public final int hashCode() {
return Objects.hash(
super.hashCode(),
this.param2,
this.param3);
}
}
public static void main(String [] args) {
A a = new A("A");
B b = new B("A", "B", "C");
System.out.println("A: " + a.hashCode());
System.out.println("B: " + b.hashCode());
}
}
मानक कार्यान्वयन कमजोर है और इसका उपयोग करने से अनावश्यक टकराव होता है। एक कल्पना करो
class ListPair {
List<Integer> first;
List<Integer> second;
ListPair(List<Integer> first, List<Integer> second) {
this.first = first;
this.second = second;
}
public int hashCode() {
return Objects.hashCode(first, second);
}
...
}
अभी,
new ListPair(List.of(a), List.of(b, c))
तथा
new ListPair(List.of(b), List.of(a, c))
के रूप में इस्तेमाल किया गुणक के रूप में एक ही है hashCode
, यहाँ पुन: 31*(a+b) + c
उपयोग किया List.hashCode
जाता है। जाहिर है, टकराव अपरिहार्य हैं, लेकिन अनावश्यक टकराव पैदा करना सिर्फ ... अनावश्यक है।
उपयोग करने के बारे में काफी स्मार्ट कुछ भी नहीं है 31
। गुणक को जानकारी खोने से बचने के लिए विषम होना चाहिए (कोई भी गुणक कम से कम सबसे महत्वपूर्ण बिट खो देता है, चार के गुणक दो को खो देते हैं, आदि)। कोई भी विषम गुणक प्रयोग करने योग्य नहीं है। छोटे गुणकों में तेजी से गणना हो सकती है (जेआईटी बदलाव और परिवर्धन का उपयोग कर सकता है), लेकिन यह देखते हुए कि गुणा में आधुनिक इंटेल / एएमडी पर केवल तीन चक्रों की विलंबता है, यह शायद ही मायने रखता है। छोटे मल्टीप्लायरों से छोटे इनपुट के लिए अधिक टकराव होता है, जो कभी-कभी एक समस्या हो सकती है।
प्राइम का उपयोग करना व्यर्थ है क्योंकि प्रिंसेस का रिंग Z / (2 ** 32) में कोई अर्थ नहीं है।
इसलिए, मैं बेतरतीब ढंग से चुनी गई बड़ी विषम संख्या (प्राइम लेने के लिए स्वतंत्र महसूस) का उपयोग करने की सलाह दूंगा। जैसा कि i86 / amd64 CPU एक हस्ताक्षरित बाइट में ऑपरेंड फिटिंग के लिए एक छोटे निर्देश का उपयोग कर सकते हैं, 109 जैसे गुणक के लिए एक छोटा गति लाभ है। टकराव को कम करने के लिए, 0x58a54cf5 जैसा कुछ लें।
अलग-अलग स्थानों पर अलग-अलग मल्टीप्लायरों का उपयोग करना सहायक है, लेकिन शायद अतिरिक्त काम को सही ठहराने के लिए पर्याप्त नहीं है।
जब हैश मानों का संयोजन होता है, तो मैं आमतौर पर संयोजन विधि का उपयोग करता हूं जो कि बढ़ावा देने वाली सी ++ लाइब्रेरी में उपयोग किया जाता है, अर्थात्:
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
यह एक समान वितरण सुनिश्चित करने का एक अच्छा काम करता है। यह सूत्र कैसे काम करता है इसकी कुछ चर्चा के लिए, StackOverflow पोस्ट देखें: जादू संख्या को बढ़ावा देने में :: hash_combine
Http://burtleburtle.net/bob/hash/doobs.html : पर विभिन्न हैश कार्यों की एक अच्छी चर्चा है
एक साधारण वर्ग के लिए अक्सर हैशकोड () को उन क्षेत्रों के आधार पर लागू करना सबसे आसान होता है जो समान (चेक) कार्यान्वयन द्वारा जाँचे जाते हैं।
public class Zam {
private String foo;
private String bar;
private String somethingElse;
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Zam otherObj = (Zam)obj;
if ((getFoo() == null && otherObj.getFoo() == null) || (getFoo() != null && getFoo().equals(otherObj.getFoo()))) {
if ((getBar() == null && otherObj. getBar() == null) || (getBar() != null && getBar().equals(otherObj. getBar()))) {
return true;
}
}
return false;
}
public int hashCode() {
return (getFoo() + getBar()).hashCode();
}
public String getFoo() {
return foo;
}
public String getBar() {
return bar;
}
}
सबसे महत्वपूर्ण बात यह है कि हैशकोड () और समतुल्य () संगत रखना: यदि बराबर () दो वस्तुओं के लिए सही लौटाता है, तो हैशकोड () एक ही मूल्य वापस करना चाहिए। यदि बराबर () गलत है, तो हैशकोड () अलग-अलग मान वापस करना चाहिए।
("abc"+""=="ab"+"c"=="a"+"bc"==""+"abc")
:। यह गंभीर दोष है। दोनों क्षेत्रों के लिए हैशकोड का मूल्यांकन करना बेहतर होगा और फिर उनमें से रैखिक संयोजन की गणना करें (अधिमानतः गुणांक के रूप में primes का उपयोग करके)।
foo
और bar
एक अनावश्यक टकराव पैदा करता है, भी।
Objects.hashCode(collection)
एक सही समाधान होना चाहिए!