Đi vào vùng xám của "chủ đề bật / tắt", nhưng cần thiết để loại bỏ sự nhầm lẫn liên quan đến đề xuất của Oscar Reyes rằng nhiều xung đột băm hơn là một điều tốt vì nó làm giảm số lượng phần tử trong HashMap. Tôi có thể hiểu sai những gì Oscar đang nói, nhưng tôi dường như không phải là người duy nhất: kdgregory, delfuego, Nash0, và tôi dường như đều có chung (sai) hiểu biết.
Nếu tôi hiểu Oscar đang nói gì về cùng một lớp với cùng một mã băm, thì anh ấy đề xuất rằng chỉ một phiên bản của một lớp có mã băm nhất định sẽ được chèn vào HashMap. Ví dụ: nếu tôi có một phiên bản SomeClass với mã băm là 1 và một phiên bản thứ hai của SomeClass với mã băm là 1, thì chỉ một phiên bản SomeClass được chèn vào.
Ví dụ về pastebin Java tại http://pastebin.com/f20af40b9 dường như chỉ ra phần trên tóm tắt chính xác những gì Oscar đang đề xuất.
Bất kể sự hiểu biết hay hiểu lầm nào, điều xảy ra là các trường hợp khác nhau của cùng một lớp sẽ không được chèn chỉ một lần vào HashMap nếu chúng có cùng một mã băm - cho đến khi xác định được liệu các khóa có bằng nhau hay không. Hợp đồng mã băm yêu cầu các đối tượng bằng nhau phải có cùng mã băm; tuy nhiên, nó không yêu cầu các đối tượng không bằng nhau phải có các mã băm khác nhau (mặc dù điều này có thể mong muốn vì các lý do khác) [1].
Sau đây là ví dụ pastebin.com/f20af40b9 (mà Oscar đề cập đến ít nhất hai lần), nhưng được sửa đổi một chút để sử dụng xác nhận JUnit thay vì dòng in. Ví dụ này được sử dụng để hỗ trợ đề xuất rằng các mã băm giống nhau gây ra xung đột và khi các lớp giống nhau, chỉ một mục nhập được tạo (ví dụ: chỉ một Chuỗi trong trường hợp cụ thể này):
@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;
}
}
Tuy nhiên, mã băm không phải là câu chuyện hoàn chỉnh. Điều mà ví dụ pastebin bỏ qua là thực tế là cả hai s
và ese
đều bằng nhau: chúng đều là chuỗi "ese". Do đó, việc chèn hoặc lấy nội dung của bản đồ bằng cách sử dụng s
hoặc ese
hoặc "ese"
làm khóa đều tương đương vì s.equals(ese) && s.equals("ese")
.
Thử nghiệm thứ hai cho thấy sai lầm khi kết luận rằng các mã băm giống hệt nhau trên cùng một lớp là lý do khiến khóa -> giá trị s -> 1
bị ghi đè ese -> 2
khi map.put(ese, 2)
được gọi trong thử nghiệm một. Trong thử nghiệm hai, s
và ese
vẫn có cùng một mã băm (như được xác minh bởi assertEquals(s.hashCode(), ese.hashCode());
) VÀ chúng là cùng một lớp. Tuy nhiên, s
và ese
là các MyString
phiên bản trong thử nghiệm này, không phải các String
phiên bản Java - với sự khác biệt duy nhất liên quan đến thử nghiệm này là bằng: String s equals String ese
trong thử nghiệm một ở trên, trong khi MyStrings s does not equal MyString ese
trong thử nghiệm hai:
@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;
}
}
Dựa trên một nhận xét sau đó, Oscar dường như đảo ngược những gì anh ấy đã nói trước đó và thừa nhận tầm quan trọng của sự bình đẳng. Tuy nhiên, có vẻ như khái niệm bình đẳng là điều quan trọng, không phải là "cùng một lớp", là không rõ ràng (tôi nhấn mạnh):
"Không hẳn. Danh sách chỉ được tạo nếu hàm băm giống nhau, nhưng khóa khác nhau. Ví dụ: nếu Chuỗi cung cấp mã băm 2345 và Số nguyên cung cấp cùng mã băm 2345, thì số nguyên được chèn vào danh sách vì Chuỗi. equals (Integer) là false. Nhưng nếu bạn có cùng một lớp (hoặc ít nhất .equals trả về true) thì cùng một mục được sử dụng. Ví dụ: new String ("một") và `new String (" one ") được sử dụng làm , sẽ sử dụng cùng một mục nhập. Trên thực tế, đây là điểm TOÀN BỘ của HashMap ở vị trí đầu tiên! Hãy tự xem: pastebin.com/f20af40b9 - Oscar Reyes "
so với các nhận xét trước đó giải quyết rõ ràng tầm quan trọng của cùng một lớp và cùng một mã băm, không đề cập đến dấu bằng:
"@delfuego: Hãy tự xem: pastebin.com/f20af40b9 Vì vậy, trong câu hỏi này, cùng một lớp đang được sử dụng (chờ một chút, cùng một lớp đang được sử dụng đúng không?). Điều này ngụ ý rằng khi cùng một hàm băm được sử dụng cùng một mục nhập được sử dụng và không có "danh sách" các mục. - Oscar Reyes "
hoặc là
"Trên thực tế, điều này sẽ làm tăng hiệu suất. Càng nhiều va chạm, càng ít mục nhập trong bảng băm. Càng ít công việc phải làm. Không phải hàm băm (trông ổn) cũng không phải bảng băm (hoạt động tốt). Tôi cá rằng nó nằm trên đối tượng sáng tạo mà hiệu suất đang giảm sút. - Oscar Reyes "
hoặc là
"@kdgregory: Có, nhưng chỉ khi va chạm xảy ra với các lớp khác nhau, đối với cùng một lớp (trường hợp này), cùng một mục được sử dụng. - Oscar Reyes"
Một lần nữa, tôi có thể hiểu nhầm những gì Oscar thực sự đang cố gắng nói. Tuy nhiên, những bình luận ban đầu của anh ấy đã gây ra sự nhầm lẫn đến mức có vẻ như cần thận trọng để làm rõ mọi thứ bằng một số thử nghiệm rõ ràng để không có nghi ngờ kéo dài.
[1] - Từ Java hiệu quả, Ấn bản thứ hai của Joshua Bloch:
Bất cứ khi nào nó được gọi trên cùng một đối tượng nhiều hơn một lần trong quá trình thực thi một ứng dụng, phương thức hashCode phải luôn trả về cùng một số nguyên, miễn là không có thông tin nào được sử dụng trong các so sánh bằng nhau trên đối tượng bị sửa đổi. Số nguyên này không cần phải duy trì nhất quán từ một lần thực thi một ứng dụng đến một lần thực thi khác của cùng một ứng dụng.
Nếu hai đối tượng bằng nhau theo phương thức s (obj ect) bằng nhau, thì việc gọi phương thức hashCode trên mỗi đối tượng trong hai đối tượng phải tạo ra cùng một kết quả số nguyên.
Không bắt buộc rằng nếu hai đối tượng là không bằng nhau theo phương thức s (Object) bằng nhau, thì việc gọi phương thức hashCode trên mỗi đối tượng trong hai đối tượng phải tạo ra kết quả số nguyên riêng biệt. Tuy nhiên, lập trình viên nên lưu ý rằng việc tạo ra các kết quả số nguyên riêng biệt cho các đối tượng không bằng nhau có thể cải thiện hiệu suất của bảng băm.