เราจะตัดสินใจเกี่ยวกับการนำhashCode()
วิธีการที่ดีที่สุดมาใช้ในการรวบรวมได้อย่างไร
collection.hashCode()
( hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/... )
เราจะตัดสินใจเกี่ยวกับการนำhashCode()
วิธีการที่ดีที่สุดมาใช้ในการรวบรวมได้อย่างไร
collection.hashCode()
( hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/... )
คำตอบ:
การใช้งานที่ดีที่สุด? นั่นเป็นคำถามที่ยากเพราะมันขึ้นอยู่กับรูปแบบการใช้งาน
สำหรับเกือบทุกกรณีการดำเนินการที่ดีอย่างสมเหตุสมผลได้ถูกเสนอในชวาที่มีประสิทธิภาพของ Josh Blochในข้อ 8 (รุ่นที่สอง) สิ่งที่ดีที่สุดคือดูที่นั่นเพราะผู้เขียนอธิบายว่าทำไมวิธีการถึงดี
สร้างint result
และกำหนดไม่ใช่ศูนย์ค่า
สำหรับทุกฟิลด์ที่ f
ทดสอบด้วยequals()
วิธีนี้ให้คำนวณรหัสแฮชc
โดย:
boolean
: คำนวณ(f ? 0 : 1)
;byte
, char
, short
หรือint
: คำนวณ(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
ซึ่งควรส่งผลให้มีการแจกจ่ายค่าแฮชอย่างเหมาะสมสำหรับสถานการณ์การใช้งานส่วนใหญ่
หากคุณพอใจกับการใช้งาน Java อย่างมีประสิทธิภาพที่แนะนำโดย dmeister คุณสามารถใช้การโทรห้องสมุดแทนการทำตามขั้นตอนของคุณเอง:
@Override
public int hashCode() {
return Objects.hashCode(this.firstName, this.lastName);
}
สิ่งนี้ต้องการ Guava ( com.google.common.base.Objects.hashCode
) หรือไลบรารี่มาตรฐานใน Java 7 ( java.util.Objects.hash
) แต่ทำงานในลักษณะเดียวกัน
hashCode
คือถ้าคุณมีการกำหนดเองequals
และนั่นคือสิ่งที่วิธีการไลบรารีเหล่านี้ได้รับการออกแบบมาอย่างถูกต้อง equals
เอกสารที่ค่อนข้างชัดเจนเกี่ยวกับพฤติกรรมของพวกเขาในความสัมพันธ์กับ การดำเนินห้องสมุดไม่ได้เรียกร้องที่จะให้อภัยคุณจากการรู้สิ่งที่ลักษณะของที่ถูกต้องhashCode
ดำเนินการเป็น - ห้องสมุดเหล่านี้ทำให้มันง่ายขึ้นสำหรับคุณที่จะดำเนินการดังกล่าวเป็นไปตามกลไกการดำเนินงานส่วนใหญ่ของกรณีที่equals
เป็น overriden
java.util.Objects.hash(...)
วิธีJDK7 มากกว่าcom.google.common.base.Objects.hashCode(...)
วิธีฝรั่ง ฉันคิดว่าคนส่วนใหญ่จะเลือกห้องสมุดมาตรฐานมากกว่าการพึ่งพาพิเศษ
hashCode()
java.lang.System.identityHashCode(...)
มันเป็นการดีกว่าที่จะใช้ฟังก์ชั่นที่จัดทำโดย Eclipse ซึ่งทำงานได้ค่อนข้างดีและคุณสามารถใช้ความพยายามและพลังงานในการพัฒนาตรรกะทางธุรกิจ
แม้ว่าสิ่งนี้จะเชื่อมโยงกับAndroid
เอกสาร (เครื่อง Wayback)และรหัสของฉันเองบน Githubแต่มันจะใช้ได้กับ Java โดยทั่วไป คำตอบของฉันคือส่วนขยายของคำตอบของ dmeisterด้วยรหัสที่อ่านและเข้าใจได้ง่ายกว่ามาก
@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
นี่เป็นข้อมูลอ้างอิงที่ดีจาก Github ของฉัน ...
@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));
}
ก่อนอื่นตรวจสอบให้แน่ใจว่ามีการใช้งานเท่ากับอย่างถูกต้อง จากบทความ IBM DeveloperWorks :
- สมมาตร: สำหรับการอ้างอิงสองรายการคือ a และ b, a.equals (b) ถ้าและถ้า b.equals (a) เท่านั้น
- Reflexivity: สำหรับการอ้างอิงที่ไม่เป็นโมฆะทั้งหมด, a.equals (a)
- ความอ่อนไหว: ถ้า a.equals (b) และ b.equals (c) ดังนั้น a.equals (c)
จากนั้นตรวจสอบให้แน่ใจว่าความสัมพันธ์ของพวกเขากับ hashCode เคารพผู้ติดต่อ (จากบทความเดียวกัน):
- ความสอดคล้องกับ hashCode (): สองวัตถุที่เท่ากันจะต้องมีค่า hashCode () เดียวกัน
สุดท้ายฟังก์ชันแฮชที่ดีควรมุ่งมั่นที่จะเข้าใกล้ฟังก์ชั่นที่เหมาะกัญชา
about8.blogspot.com คุณพูดว่า
ถ้าเท่ากับ () ผลตอบแทนจริงสำหรับสองวัตถุแล้ว hashCode () ควรกลับค่าเดียวกัน ถ้าเท่ากับ () ส่งคืนค่าเท็จ hashCode () ควรคืนค่าต่างกัน
ฉันไม่เห็นด้วยกับคุณ หากวัตถุสองชิ้นมีแฮชโค้ดเดียวกันมันไม่จำเป็นต้องหมายความว่าพวกมันเท่ากัน
ถ้า A เท่ากับ B ดังนั้น A.hashcode ต้องเท่ากับ B.hascode
แต่
ถ้า A.hashcode เท่ากับ B.hascode มันไม่ได้หมายความว่า A ต้องเท่ากับ B
(A != B) and (A.hashcode() == B.hashcode())
นั่นคือสิ่งที่เราเรียกว่าการชนกันของฟังก์ชันแฮช เป็นเพราะโคโดเมนของฟังก์ชันแฮช จำกัด เสมอในขณะที่โดเมนมักจะไม่ ยิ่งโคโดเมนใหญ่มากเท่าไรการชนก็จะน้อยลง ฟังก์ชั่นแฮชที่ดีควรคืนค่าแฮชที่แตกต่างกันสำหรับวัตถุที่แตกต่างกันโดยมีความเป็นไปได้มากที่สุดที่ทำได้เมื่อกำหนดขนาดโคโดเมน มันแทบจะไม่สามารถรับประกันได้อย่างเต็มที่ว่า
หากคุณใช้ eclipse คุณสามารถสร้างequals()
และhashCode()
ใช้:
แหล่งที่มา -> สร้าง hashCode () และเท่ากับ ()
การใช้ฟังก์ชั่นนี้คุณสามารถเลือกฟิลด์ที่คุณต้องการใช้สำหรับการคำนวณความเท่าเทียมกันและรหัสแฮชและ Eclipse จะสร้างวิธีการที่สอดคล้องกัน
มีการดำเนินงานที่ดีของเรื่องที่มีประสิทธิภาพ Java 's hashcode()
และequals()
ตรรกะในApache คอมมอนส์แลง กร้าHashCodeBuilderและEqualsBuilder
Objects
ชั้นเรียนให้hash(Object ..args)
และequals()
วิธีการจาก Java7 บน เหมาะสำหรับแอปพลิเคชันที่ใช้ jdk 1.7+
IdentityHashMap
) FWIW ฉันใช้ hashCode ตามรหัสและเท่ากับเอนทิตีทั้งหมด
เพียงบันทึกย่อเพื่อให้ได้คำตอบที่ละเอียดยิ่งขึ้น (ในรูปของโค้ด):
ถ้าฉันพิจารณาคำถามว่าจะทำอย่างไรฉันจะสร้างตารางแฮชในจาวาและโดยเฉพาะอย่างยิ่งรายการคำถามที่พบบ่อยของ jGuruฉันเชื่อว่าเกณฑ์อื่น ๆ ที่ควรใช้รหัสแฮชคือ:
หากฉันเข้าใจคำถามของคุณถูกต้องคุณจะมีคลาสคอลเล็กชันที่กำหนดเอง (เช่นคลาสใหม่ที่ขยายจากส่วนต่อประสาน Collection) และคุณต้องการใช้เมธอด hashCode ()
หากคลาสการรวบรวมของคุณขยาย AbstractList คุณไม่ต้องกังวลเกี่ยวกับมันมีการใช้งานเท่ากับ () และ hashCode () ที่ทำงานโดยการวนซ้ำผ่านวัตถุทั้งหมดและเพิ่ม hashCodes () เข้าด้วยกัน
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;
}
ตอนนี้ถ้าสิ่งที่คุณต้องการเป็นวิธีที่ดีที่สุดในการคำนวณรหัสแฮชสำหรับคลาสเฉพาะฉันใช้ตัวดำเนินการ ^ (bitwise exclusive หรือ) ในการประมวลผลทุกฟิลด์ที่ฉันใช้ในวิธีการเท่ากับ:
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();
(คุณสามารถรับ hashCode โดยตรงจาก int ใน Java วันนี้หรือไม่ฉันคิดว่าการ autocasting บางอย่าง .. ถ้าเป็นกรณีนี้ให้ข้าม toString มันน่าเกลียด)
foo
และนำไปสู่การเดียวกันbar
AFAIK hashCode
ของคุณtoString
ไม่ได้คอมไพล์และถ้าเป็นเช่นนั้นแสดงว่ามันไม่มีประสิทธิภาพมาก สิ่งที่ชอบ109 * getFoo().hashCode() + 57 * getBar().hashCode()
นั้นเร็วขึ้นง่ายขึ้นและไม่เกิดการชนที่ไม่จำเป็น
ตามที่คุณขอเฉพาะคอลเล็กชันฉันต้องการเพิ่มมุมมองที่คำตอบอื่น ๆ ยังไม่ได้กล่าวถึง: HashMap ไม่ได้คาดหวังว่ากุญแจของพวกเขาจะเปลี่ยนรหัส hashcode เมื่อมีการเพิ่มลงในคอลเลกชัน จะเอาชนะวัตถุประสงค์ทั้งหมด ...
ใช้วิธีการสะท้อนบน Apache Commons EqualsBuilderและHashCodeBuilder
ฉันใช้ wrapper เล็ก ๆ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&sig=kR0n73DHJOn-D77qGj0wOxAxiZw&hl=en&sa=X&oi=book_result&resnum=1&ct=result ) มีเคล็ดลับที่ดี ในนั้นสำหรับการติดตั้งแฮชโค้ด (ข้อ 9 ฉันคิดว่า ... )
ฉันชอบใช้วิธีอรรถประโยชน์จากGoogle คอลเลกชัน lib จากคลาสอ็อบเจ็กต์ที่ช่วยให้ฉันรักษาโค้ดของฉันให้สะอาด บ่อยครั้งมากequals
และhashcode
วิธีการที่ทำจากแม่แบบของ IDE ดังนั้นจึงไม่สะอาดในการอ่าน
นี่คือการสาธิตวิธี JDK 1.7+ อีกหนึ่งเรื่องที่มีการบันทึก superclass ฉันเห็นว่าค่อนข้างมั่นใจกับ Object class hashCode () ซึ่งมีการอ้างอิงการพึ่งพา 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
ไม่มีอะไรที่มีนัยสำคัญเกี่ยวกับการใช้สมาร์ทเป็น ตัวคูณจะต้องเป็นเลขคี่เพื่อหลีกเลี่ยงการสูญเสียข้อมูล (ตัวคูณใด ๆ ที่สูญหายอย่างน้อยบิตที่สำคัญที่สุดทวีคูณของสี่สูญเสียสองเป็นต้น) ตัวคูณคี่ใด ๆ สามารถใช้งานได้ ตัวคูณขนาดเล็กอาจนำไปสู่การคำนวณที่เร็วขึ้น (JIT สามารถใช้การเลื่อนและการเพิ่มเติม) แต่เนื่องจากการคูณมีความหน่วงแฝงเพียงสามรอบในปัจจุบันของ Intel / AMD สิ่งนี้แทบจะไม่สำคัญ ตัวคูณขนาดเล็กยังนำไปสู่การชนกันมากขึ้นสำหรับอินพุตขนาดเล็กซึ่งบางครั้งอาจมีปัญหา
การใช้งานไพรม์นั้นไม่มีจุดหมายเนื่องจากช่วงเวลาไม่มีความหมายในวงแหวน Z / (2 ** 32)
ดังนั้นฉันขอแนะนำให้ใช้หมายเลขคี่ขนาดใหญ่ที่สุ่มเลือก (อย่าลังเลที่จะเลือกนายก) ในขณะที่ซีพียู i86 / amd64 สามารถใช้คำสั่งที่สั้นกว่าสำหรับตัวถูกดำเนินการติดตั้งในไบต์เดียวที่ลงนามจึงมีข้อได้เปรียบความเร็วเล็ก ๆ สำหรับตัวคูณทวีคูณเช่น 109 สำหรับการลดการชนให้ใช้ 0x58a54cf5
การใช้ตัวทวีคูณที่แตกต่างกันในที่ต่าง ๆ มีประโยชน์ แต่อาจไม่เพียงพอที่จะปรับการทำงานเพิ่มเติมให้เหมาะสม
เมื่อรวมค่าแฮชฉันมักจะใช้วิธีการรวมที่ใช้ในห้องสมุดเพิ่ม c ++ คือ:
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
นี่เป็นงานที่ค่อนข้างดีในการรับรองการกระจายอย่างสม่ำเสมอ สำหรับการอภิปรายเกี่ยวกับวิธีการทำงานของสูตรนี้ให้ดูที่โพสต์ StackOverflow: หมายเลข Magic เพื่อเพิ่ม :: hash_combine
มีการอภิปรายที่ดีเกี่ยวกับฟังก์ชั่นแฮชต่างๆได้ที่: http://burtleburtle.net/bob/hash/doobs.html
สำหรับคลาสที่เรียบง่ายมักจะง่ายที่สุดในการใช้ hashCode () ตามฟิลด์คลาสที่ถูกตรวจสอบโดยการดำเนินการเท่ากับ ()
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;
}
}
สิ่งที่สำคัญที่สุดคือการทำให้ hashCode () และเท่ากับ () สอดคล้องกัน: ถ้าเท่ากับ () ผลตอบแทนจริงสำหรับวัตถุสองแล้ว hashCode () ควรกลับค่าเดียวกัน ถ้าเท่ากับ () ส่งคืนค่าเท็จ hashCode () ควรคืนค่าต่างกัน
("abc"+""=="ab"+"c"=="a"+"bc"==""+"abc")
ถ้าแฮชโค้ดจะถูกสร้างขึ้นครั้งเดียวจากการเรียงต่อกันของสองสายมันเป็นเรื่องง่ายมากที่จะสร้างฝูงชน: มันเป็นข้อบกพร่องที่รุนแรง มันจะเป็นการดีกว่าที่จะประเมินค่าแฮชโค้ดสำหรับทั้งสองฟิลด์จากนั้นคำนวณชุดค่าผสมเชิงเส้นของฟิลด์เหล่านั้น (ควรใช้เฉพาะช่วงเวลาเป็นค่าสัมประสิทธิ์)
foo
และbar
สร้างการชนที่ไม่มีความจำเป็นเช่นกัน
Objects.hashCode(collection)
น่าจะเป็นทางออกที่สมบูรณ์แบบ!