Làm thế nào để một Java HashMap xử lý các đối tượng khác nhau với cùng một mã băm?


232

Theo sự hiểu biết của tôi, tôi nghĩ:

  1. Việc hai đối tượng có cùng một mã băm là hoàn toàn hợp pháp.
  2. Nếu hai đối tượng bằng nhau (sử dụng phương thức equals ()) thì chúng có cùng một mã băm.
  3. Nếu hai đối tượng không bằng nhau thì chúng không thể có cùng một mã băm

Tôi có đúng không?

Bây giờ nếu đúng, tôi có câu hỏi sau: Nội HashMapbộ sử dụng mã băm của đối tượng. Vì vậy, nếu hai đối tượng có thể có cùng một mã băm, thì làm thế nào có thể HashMaptheo dõi khóa mà nó sử dụng?

Ai đó có thể giải thích cách HashMapbên trong sử dụng mã băm của đối tượng?


30
Đối với bản ghi: # 1 và # 2 đúng, # 3 sai: Hai đối tượng không bằng nhau có thể có cùng mã băm.
Joachim Sauer

7
# 1 và # 3 trái ngược nhau ngay cả
Delfic. 14/09/2016

Thật vậy, nếu # 2 không được theo sau, thì việc triển khai bằng () (hoặc được cho là Mã băm ()) là không chính xác.
Joachim Sauer

Câu trả lời:


353

Một bản đồ băm hoạt động như thế này (cái này được đơn giản hóa một chút, nhưng nó minh họa cơ chế cơ bản):

Nó có một số "nhóm" mà nó sử dụng để lưu trữ các cặp khóa-giá trị. Mỗi nhóm có một số duy nhất - đó là những gì xác định nhóm. Khi bạn đặt một cặp khóa-giá trị vào bản đồ, bản đồ băm sẽ xem xét mã băm của khóa và lưu trữ cặp đó trong nhóm mà mã định danh là mã băm của khóa. Ví dụ: Mã băm của khóa là 235 -> cặp được lưu trữ trong nhóm số 235. (Lưu ý rằng một nhóm có thể lưu trữ nhiều hơn thì một cặp khóa-giá trị).

Khi bạn tra cứu một giá trị trong bản đồ băm, bằng cách cấp cho nó một khóa, trước tiên nó sẽ xem mã băm của khóa mà bạn đã cung cấp. Sau đó, bản đồ băm sẽ xem xét nhóm tương ứng và sau đó nó sẽ so sánh khóa mà bạn đã cung cấp với các khóa của tất cả các cặp trong nhóm, bằng cách so sánh chúng vớiequals() .

Bây giờ bạn có thể thấy cách này rất hiệu quả để tìm kiếm các cặp khóa-giá trị trong bản đồ: bằng mã băm của khóa, bản đồ băm ngay lập tức biết cần tìm trong nhóm nào, do đó nó chỉ phải kiểm tra những gì trong nhóm đó.

Nhìn vào cơ chế trên, bạn cũng có thể thấy những yêu cầu nào là cần thiết đối với các phương thức hashCode()equals()khóa:

  • Nếu hai khóa giống nhau ( equals()trả về truekhi bạn so sánh chúng), hashCode()phương thức của chúng phải trả về cùng một số. Nếu các khóa vi phạm điều này, thì các khóa bằng nhau có thể được lưu trữ trong các nhóm khác nhau và bản đồ băm sẽ không thể tìm thấy các cặp khóa-giá trị (vì nó sẽ tìm trong cùng một nhóm).

  • Nếu hai khóa khác nhau, thì không quan trọng là mã băm của chúng có giống nhau hay không. Chúng sẽ được lưu trữ trong cùng một nhóm nếu mã băm của chúng giống nhau và trong trường hợp này, bản đồ băm sẽ sử dụng equals()để phân biệt chúng.


4
bạn đã viết "và hashmap sẽ không thể tìm thấy các cặp khóa-giá trị (vì nó sẽ tìm trong cùng một nhóm)." Bạn có thể giải thích nó sẽ xem xét trong cùng một thùng nói rằng hai đối tượng bằng nhau đó là t1 và t2 và chúng bằng nhau và t1 và t2 có mã băm tương ứng là h1 và h2. Vì vậy, t1.equals (t2) = true và h1! = H2 Vì vậy, khi hashmap sẽ tìm kiếm t1, nó sẽ tìm kiếm trong nhóm h1 và cho t2 trong nhóm t2?
Geek

20
Nếu hai khóa bằng nhau nhưng hashCode()phương thức của chúng trả về các mã băm khác nhau, thì các equals()hashCode()phương thức của lớp khóa vi phạm hợp đồng và bạn sẽ nhận được kết quả kỳ lạ khi sử dụng các khóa đó trong a HashMap.
Jesper

Mỗi nhóm có thể có nhiều cặp Giá trị chính, được sử dụng trong nội bộ danh sách được liên kết. Nhưng sự nhầm lẫn của tôi là - xô ở đây là gì? Nó sử dụng cấu trúc dữ liệu nào trong nội bộ? Có mối liên hệ nào giữa các thùng không?
Ankit Sharma

1
@AnkitSharma Nếu bạn thực sự muốn biết tất cả các chi tiết, hãy tra cứu mã nguồn HashMapmà bạn có thể tìm thấy trong tệp src.ziptrong thư mục cài đặt JDK của mình.
Jesper

2
@ 1290 Mối quan hệ duy nhất giữa các khóa trong cùng một nhóm là chúng có cùng mã băm.
Jesper

90

Khẳng định thứ ba của bạn là không chính xác.

Hoàn toàn hợp pháp khi hai đối tượng không bằng nhau có cùng mã băm. Nó được sử dụng HashMapnhư một "bộ lọc vượt qua đầu tiên" để bản đồ có thể nhanh chóng tìm thấy các mục nhập khả thi bằng khóa được chỉ định. Các khóa có cùng mã băm sau đó sẽ được kiểm tra xem có bình đẳng với khóa được chỉ định hay không.

Bạn sẽ không muốn có yêu cầu rằng hai đối tượng không bằng nhau không thể có cùng một mã băm, vì nếu không điều đó sẽ giới hạn bạn ở 2 32 đối tượng có thể. (Điều đó cũng có nghĩa là các loại khác nhau thậm chí không thể sử dụng các trường của đối tượng để tạo mã băm, vì các lớp khác có thể tạo cùng một hàm băm.)


6
Làm thế nào bạn đến được 2 ^ 32 vật thể?
Geek

7
Tôi đến trễ, nhưng đối với những vẫn tự hỏi: Một hashcode trong Java là một int, và một int có 2 ^ 32 giá trị có thể
Xerus

72

Sơ đồ cấu trúc HashMap

HashMaplà một mảng các Entryđối tượng.

Coi HashMapnhư một mảng các đối tượng.

Hãy xem đây Objectlà gì :

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
… 
}

Mỗi Entryđối tượng đại diện cho một cặp khóa-giá trị. Trường nexttham chiếu đến một Entryđối tượng khác nếu một nhóm có nhiều hơn một Entry.

Đôi khi có thể xảy ra trường hợp mã băm cho 2 đối tượng khác nhau giống nhau. Trong trường hợp này, hai đối tượng sẽ được lưu trong một nhóm và sẽ được trình bày dưới dạng danh sách được liên kết. Điểm vào là đối tượng được thêm gần đây. Đối tượng này tham chiếu đến một đối tượng khác với nexttrường, v.v. Mục cuối cùng đề cập đếnnull .

Khi bạn tạo một HashMapvới hàm tạo mặc định

HashMap hashMap = new HashMap();

Mảng được tạo với kích thước 16 và cân bằng tải 0,75 mặc định.

Thêm một cặp khóa-giá trị mới

  1. Tính toán mã băm cho khóa
  2. Tính toán vị trí hash % (arrayLength-1)cần đặt phần tử (số thùng)
  3. Nếu bạn cố gắng thêm một giá trị bằng khóa đã được lưu vào HashMap, thì giá trị sẽ bị ghi đè.
  4. Nếu không, phần tử sẽ được thêm vào nhóm.

Nếu nhóm đã có ít nhất một phần tử, thì một phần tử mới sẽ được thêm vào và đặt ở vị trí đầu tiên của nhóm. Nó lànext đề cập đến phần tử cũ.

Xóa

  1. Tính toán mã băm cho khóa đã cho
  2. Tính số thùng hash % (arrayLength-1)
  3. Nhận một tham chiếu đến đối tượng Entry đầu tiên trong nhóm và bằng phương pháp equals, hãy lặp lại tất cả các mục trong nhóm đã cho. Cuối cùng, chúng tôi sẽ tìm thấy chính xác Entry. Nếu không tìm thấy phần tử mong muốn, hãy trả vềnull

4
Điều này là sai, hash % (arrayLength-1)nó sẽ được hash % arrayLength. Nhưng thực tế là như vậy hash & (arrayLength-1) . Đó là, bởi vì nó sử dụng lũy ​​thừa của hai ( 2^n) cho độ dài mảng, chiếm nít bit quan trọng nhất.
weston

Tôi nghĩ đó không phải là một mảng các đối tượng Thực thể mà là một mảng LinkedList / Tree. Và bên trong mỗi cây có các đối tượng Thực thể.
Mudit bhaintwal

@shevchyk tại sao chúng tôi lưu trữ khóa và băm? công dụng của chúng là gì? Không phải chúng ta đang lãng phí bộ nhớ ở đây?
roottraveller

hashset nội bộ sử dụng hashmap. quy tắc thêm và xóa của hashmap có tốt cho hashset không?
trao đổi quá mức

2
@weston không chỉ có vậy, hashCode là một inttrong đó tất nhiên có thể tiêu cực, làm modulo trên một số âm sẽ cung cấp cho bạn một số âm
Eugene

35

Bạn có thể tìm thấy thông tin tuyệt vời tại http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html

Tóm tắt:

HashMap hoạt động trên nguyên tắc băm

put (key, value): HashMap lưu trữ cả đối tượng key và value dưới dạng Map.Entry. Hashmap áp dụng mã băm (khóa) để lấy nhóm. nếu có va chạm, HashMap sử dụng LinkedList để lưu trữ đối tượng.

get (key): HashMap sử dụng mã băm của Key Object để tìm ra vị trí thùng và sau đó gọi phương thức key.equals () để xác định đúng nút trong LinkedList và trả về đối tượng giá trị liên quan cho khóa đó trong Java HashMap.


3
Tôi tìm thấy câu trả lời cung cấp bởi Jasper tốt hơn, tôi cảm thấy blog là nhiều hơn đối với việc xử lý cuộc phỏng vấn, hơn sự hiểu biết về khái niệm
Narendra N

@NarendraN Tôi đồng ý với bạn.
Abhijit Gaikwad

22

Đây là mô tả sơ bộ về HashMapcơ chế của, dành cho Java 8phiên bản, (nó có thể hơi khác so với Java 6) .


Cấu trúc dữ liệu

  • Bảng
    băm Giá trị băm được tính quahash() khóa và nó quyết định nhóm nào của sẽ sử dụng cho một khóa nhất định.
  • Danh sách được liên kết (riêng lẻ)
    Khi số lượng phần tử trong một nhóm nhỏ, danh sách được liên kết đơn lẻ sẽ được sử dụng.
  • Cây đỏ-đen
    Khi số lượng phần tử trong một thùng lớn, cây đỏ-đen được sử dụng.

Lớp học (nội bộ)

  • Map.Entry
    Đại diện cho một thực thể duy nhất trong bản đồ, thực thể khóa / giá trị.
  • HashMap.Node
    Phiên bản danh sách được liên kết của nút.

    Nó có thể đại diện cho:

    • Một thùng băm.
      Vì nó có thuộc tính băm.
    • Một nút trong danh sách liên kết đơn, (do đó cũng đứng đầu danh sách liên kết ) .
  • HashMap.TreeNode
    Phiên bản cây của nút.

Trường (nội bộ)

  • Node[] table
    Bảng nhóm, (đầu danh sách được liên kết).
    Nếu một thùng không chứa các phần tử, thì nó là null, do đó chỉ chiếm không gian của một tham chiếu.
  • Set<Map.Entry> entrySet Tập hợp các thực thể.
  • int size
    Số lượng thực thể.
  • float loadFactor
    Cho biết mức độ đầy đủ của bảng băm trước khi thay đổi kích thước.
  • int threshold
    Kích thước tiếp theo để thay đổi kích thước.
    Công thức:threshold = capacity * loadFactor

Phương pháp (nội bộ)

  • int hash(key)
    Tính toán băm theo khóa.
  • Làm thế nào để ánh xạ hash với bucket?
    Sử dụng logic sau:

    static int hashToBucket(int tableSize, int hash) {
        return (tableSize - 1) & hash;
    }
    

Về năng lực

Trong bảng băm, dung lượng có nghĩa là số lượng nhóm, nó có thể được lấy từ table.length.
Cũng có thể được tính toán thông qua thresholdloadFactor, do đó không cần phải được định nghĩa như một trường lớp.

Có thể nhận được công suất hiệu quả thông qua: capacity()


Hoạt động

  • Tìm thực thể bằng khóa.
    Trước tiên, hãy tìm nhóm theo giá trị băm, sau đó lặp danh sách liên kết hoặc tìm kiếm cây được sắp xếp.
  • Thêm thực thể bằng khóa.
    Đầu tiên, hãy tìm nhóm theo giá trị băm của khóa.
    Sau đó, hãy thử tìm giá trị:
    • Nếu tìm thấy, hãy thay thế giá trị.
    • Nếu không, hãy thêm một nút mới vào đầu danh sách được liên kết hoặc chèn vào cây đã sắp xếp.
  • Thay đổi kích thước
    Khi thresholdđạt đến, sẽ nhân đôi dung lượng của table.lengthbảng băm ( ), sau đó thực hiện băm lại trên tất cả các phần tử để xây dựng lại bảng.
    Đây có thể là một hoạt động tốn kém.

Hiệu suất

  • get & put
    Độ phức tạp về thời gian là O(1)vì:
    • Do đó, nhóm được truy cập thông qua chỉ mục mảng O(1).
    • Danh sách được liên kết trong mỗi nhóm có độ dài nhỏ, do đó có thể xem như O(1) .
    • Kích thước cây cũng bị giới hạn, vì sẽ mở rộng dung lượng & băm lại khi số lượng phần tử tăng lên, vì vậy có thể xem nó là O(1), không O(log N).

Bạn có thể xin vui lòng cho một ví dụ như thế nào có thời gian phức tạp O (1)
Jitendra

@jsroyal Điều này có thể giải thích sự phức tạp rõ ràng hơn: en.wikipedia.org/wiki/Hash_table . Nhưng tóm lại: việc tìm nhóm đích là O (1), bởi vì bạn tìm nó theo chỉ mục trong một mảng; thì trong một nhóm, số lượng phần tử là nhỏ & trung bình là một số không đổi mặc dù tổng số phần tử trong toàn bộ bảng băm, vì vậy việc tìm kiếm phần tử đích trong một nhóm cũng là O (1); do đó, O (1) + O (1) = O (1).
Eric Wang

15

Mã băm xác định nhóm nào cho bản đồ băm cần kiểm tra. Nếu có nhiều hơn một đối tượng trong nhóm thì tìm kiếm tuyến tính sẽ được thực hiện để tìm mục nào trong nhóm bằng với mục mong muốn (sử dụng equals()phương thức).

Nói cách khác, nếu bạn có một mã băm hoàn hảo thì quyền truy cập vào bản đồ băm là không đổi, bạn sẽ không bao giờ phải lặp qua một nhóm (về mặt kỹ thuật, bạn cũng sẽ phải có MAX_INT nhóm, việc triển khai Java có thể chia sẻ một vài mã băm trong cùng một nhóm để cắt giảm yêu cầu về không gian). Nếu bạn có mã băm kém nhất (luôn trả về cùng một số) thì quyền truy cập vào bản đồ băm của bạn sẽ trở thành tuyến tính vì bạn phải tìm kiếm qua mọi mục trong bản đồ (tất cả chúng đều nằm trong cùng một nhóm) để có được thứ bạn muốn.

Hầu hết thời gian, một mã băm được viết tốt không hoàn hảo nhưng đủ duy nhất để cung cấp cho bạn ít nhiều quyền truy cập liên tục.


11

Bạn nhầm ở điểm thứ ba. Hai mục nhập có thể có cùng mã băm nhưng không bằng nhau. Hãy xem việc triển khai HashMap.get từ OpenJdk . Bạn có thể thấy rằng nó kiểm tra các băm có bằng nhau và các khóa bằng nhau hay không. Nếu điểm ba đúng, thì sẽ không cần thiết phải kiểm tra xem các khóa có bằng nhau hay không. Mã băm được so sánh trước khóa vì mã trước là so sánh hiệu quả hơn.

Nếu bạn muốn tìm hiểu thêm một chút về điều này, hãy xem bài viết Wikipedia về giải quyết xung đột Địa chỉ mở , mà tôi tin rằng đó là cơ chế mà việc triển khai OpenJdk sử dụng. Cơ chế đó khác một cách tinh tế so với cách tiếp cận "xô" mà một trong những câu trả lời khác đề cập.


6
import java.util.HashMap;

public class Students  {
    String name;
    int age;

    Students(String name, int age ){
        this.name = name;
        this.age=age;
    }

    @Override
    public int hashCode() {
        System.out.println("__hash__");
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("__eq__");
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Students other = (Students) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    public static void main(String[] args) {

        Students S1 = new Students("taj",22);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Output:

__ hash __

116232

__ hash __

116201

__ hash __

__ hash __

2

Vì vậy, ở đây chúng ta thấy rằng nếu cả hai đối tượng S1 và S2 có nội dung khác nhau, thì chúng ta khá chắc chắn rằng phương thức Hashcode bị ghi đè của chúng ta sẽ tạo ra Hashcode khác nhau (116232,11601) cho cả hai đối tượng. NGAY BÂY GIỜ vì có các mã băm khác nhau, vì vậy bạn sẽ không cần phải gọi phương thức EQUALS. Bởi vì một mã Hashcode khác ĐẢM BẢO nội dung KHÁC NHAU trong một đối tượng.

    public static void main(String[] args) {

        Students S1 = new Students("taj",21);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Now lets change out main method a little bit. Output after this change is 

__ hash __

116201

__ hash __

116201

__ hash __

__ hash __

__ eq __

1
We can clearly see that equal method is called. Here is print statement __eq__, since we have same hashcode, then content of objects MAY or MAY not be similar. So program internally  calls Equal method to verify this. 


Conclusion 
If hashcode is different , equal method will not get called. 
if hashcode is same, equal method will get called.

Thanks , hope it helps. 

4

hai đối tượng bằng nhau, ngụ ý rằng chúng có cùng một mã băm, nhưng không phải ngược lại.

2 đối tượng bằng nhau ------> chúng có cùng mã băm

2 đối tượng có cùng mã băm ---- xxxxx -> chúng KHÔNG bằng nhau

Cập nhật Java 8 trong HashMap-

bạn thực hiện thao tác này trong mã của mình -

myHashmap.put("old","old-value");
myHashMap.put("very-old","very-old-value");

vì vậy, giả sử mã băm của bạn được trả về cho cả hai khóa "old""very-old"giống nhau. Rồi điều gì sẽ xảy ra.

myHashMaplà một HashMap và giả sử rằng ban đầu bạn không chỉ định dung lượng của nó. Vì vậy, dung lượng mặc định cho mỗi java là 16. Vì vậy, ngay khi bạn khởi tạo hashmap bằng từ khóa mới, nó đã tạo ra 16 nhóm. bây giờ khi bạn thực hiện câu lệnh đầu tiên-

myHashmap.put("old","old-value");

sau đó "old"mã băm cho được tính toán và vì mã băm cũng có thể là số nguyên rất lớn, do đó, nội bộ java đã thực hiện điều này - (băm là mã băm ở đây và >>> là dịch chuyển bên phải)

hash XOR hash >>> 16

vì vậy, để đưa ra một bức tranh lớn hơn, nó sẽ trả về một số chỉ mục, sẽ nằm trong khoảng từ 0 đến 15. Bây giờ cặp giá trị khóa của bạn "old""old-value" sẽ được chuyển đổi thành biến cá thể khóa và giá trị của đối tượng Entry. và sau đó đối tượng mục nhập này sẽ được lưu trữ trong thùng, hoặc bạn có thể nói rằng tại một chỉ mục cụ thể, đối tượng mục nhập này sẽ được lưu trữ.

FYI- Entry là một lớp trong giao diện Bản đồ- Map.Entry, với những chữ ký / định nghĩa này

class Entry{
          final Key k;
          value v;
          final int hash;
          Entry next;
}

bây giờ khi bạn thực hiện câu lệnh tiếp theo -

myHashmap.put("very-old","very-old-value");

"very-old"cung cấp cùng một mã băm "old", vì vậy, cặp giá trị khóa mới này lại được gửi đến cùng một chỉ mục hoặc cùng một nhóm. Nhưng vì thùng này không trống, nên nextbiến của đối tượng Entry được sử dụng để lưu trữ cặp giá trị khóa mới này.

và điều này sẽ được lưu trữ dưới dạng danh sách liên kết cho mọi đối tượng có cùng một mã băm, nhưng một TRIEFY_THRESHOLD được chỉ định với giá trị 6. vì vậy sau khi đạt đến điều này, danh sách được liên kết được chuyển đổi thành cây cân bằng (cây đỏ-đen) với phần tử đầu tiên là nguồn gốc.


câu trả lời tuyệt vời (y)
Sudhanshu bò tót

2

Mỗi đối tượng Entry đại diện cho cặp khóa-giá trị. Trường tiếp theo đề cập đến đối tượng Mục nhập khác nếu một nhóm có nhiều hơn 1 Mục nhập.

Đôi khi có thể xảy ra trường hợp Mã băm cho 2 đối tượng khác nhau giống nhau. Trong trường hợp này, 2 đối tượng sẽ được lưu trong một nhóm và sẽ được trình bày dưới dạng LinkedList. Điểm vào là đối tượng được thêm gần đây. Đối tượng này tham chiếu đến đối tượng khác với trường tiếp theo và như vậy một. Mục nhập cuối cùng đề cập đến null. Khi bạn tạo HashMap với hàm tạo mặc định

Mảng được tạo với kích thước 16 và cân bằng tải 0,75 mặc định.

nhập mô tả hình ảnh ở đây

(Nguồn)


1

Bản đồ băm hoạt động trên nguyên tắc băm

Phương thức HashMap get (Key k) gọi phương thức hashCode trên đối tượng khóa và áp dụng hashValue trả về cho hàm băm tĩnh của chính nó để tìm vị trí thùng (mảng sao lưu) nơi các khóa và giá trị được lưu trữ dưới dạng một lớp lồng nhau được gọi là Mục nhập (Bản đồ). Đầu vào). Vì vậy, bạn đã kết luận rằng từ dòng trước đó Cả khóa và giá trị đều được lưu trữ trong thùng dưới dạng một đối tượng Entry. Vì vậy, nghĩ rằng Chỉ giá trị được lưu trữ trong thùng là không đúng và sẽ không tạo ấn tượng tốt cho người phỏng vấn.

  • Bất cứ khi nào chúng ta gọi phương thức get (Key k) trên đối tượng HashMap. Đầu tiên, nó kiểm tra xem khóa có phải là null hay không. Lưu ý rằng chỉ có thể có một khóa rỗng trong HashMap.

Nếu khóa rỗng, thì khóa Null luôn ánh xạ tới băm 0, do đó chỉ mục 0.

Nếu khóa không phải là null thì nó sẽ gọi hàm băm trên đối tượng khóa, xem dòng 4 trong phương thức trên, tức là key.hashCode (), vì vậy sau key.hashCode () trả về hashValue, dòng 4 trông như thế nào

            int hash = hash(hashValue)

và bây giờ, nó áp dụng hashValue trả về vào hàm băm của chính nó.

Chúng ta có thể tự hỏi tại sao chúng ta lại tính toán giá trị băm bằng cách sử dụng hàm băm (hashValue). Câu trả lời là Nó bảo vệ chống lại các hàm băm kém chất lượng.

Bây giờ giá trị băm cuối cùng được sử dụng để tìm vị trí thùng mà tại đó đối tượng Entry được lưu trữ. Đối tượng mục nhập lưu trữ trong nhóm như thế này (băm, khóa, giá trị, bucketindex)


1

Tôi sẽ không đi sâu vào chi tiết về cách HashMap hoạt động, nhưng sẽ đưa ra một ví dụ để chúng ta có thể nhớ cách hoạt động của HashMap bằng cách liên hệ nó với thực tế.

Chúng tôi có Khóa, Giá trị, Mã HashCode và nhóm.

Đôi khi, chúng tôi sẽ liên hệ từng người trong số họ với những điều sau:

  • Bucket -> A Society
  • HashCode -> Địa chỉ của hội (luôn luôn duy nhất)
  • Giá trị -> Một ngôi nhà trong xã hội
  • Chìa khóa -> Địa chỉ nhà.

Sử dụng Map.get (key):

Stevie muốn đến nhà người bạn của mình (Josse), người sống trong một biệt thự trong một hội VIP, gọi là Hội JavaLovers. Địa chỉ của Josse là SSN của anh ấy (địa chỉ này khác với mọi người). Có một chỉ mục được duy trì trong đó chúng tôi tìm ra tên của Hội dựa trên SSN. Chỉ mục này có thể được coi là một thuật toán để tìm ra HashCode.

  • Tên của Hội SSN
  • 92313 (Josse's) - JavaLovers
  • 13214 - AngularJSLovers
  • 98080 - JavaLovers
  • 53808 - Sinh học

  1. SSN (khóa) này trước tiên cung cấp cho chúng ta một HashCode (từ bảng chỉ mục) không có gì khác ngoài tên của Hội.
  2. Giờ đây, nhiều ngôi nhà có thể ở trong cùng một xã hội, vì vậy HashCode có thể là chung.
  3. Giả sử, Hội chung cho hai ngôi nhà, làm cách nào để xác định ngôi nhà mà chúng tôi sẽ đến, vâng, bằng cách sử dụng khóa (SSN) không là gì ngoài địa chỉ Ngôi nhà

Sử dụng Map.put (key, Value)

Điều này tìm thấy một xã hội phù hợp cho Giá trị này bằng cách tìm Mã HashCode và sau đó giá trị được lưu trữ.

Tôi hy vọng điều này sẽ giúp ích và điều này được mở để sửa đổi.


0

Đó sẽ là một câu trả lời dài, hãy uống một ly và đọc tiếp…

Hashing là tất cả về việc lưu trữ một cặp khóa-giá trị trong bộ nhớ để có thể đọc và ghi nhanh hơn. Nó lưu trữ các khóa trong một mảng và các giá trị trong một LinkedList.

Giả sử tôi muốn lưu trữ 4 cặp giá trị khóa -

{
“girl” => “ahhan” , 
“misused” => “Manmohan Singh” , 
“horsemints” => “guess what”, 
“no” => “way”
}

Vì vậy, để lưu trữ các khóa, chúng ta cần một mảng 4 phần tử. Bây giờ làm cách nào để ánh xạ một trong 4 khóa này thành 4 chỉ mục mảng (0,1,2,3)?

Vì vậy, java tìm mã băm của các khóa riêng lẻ và ánh xạ chúng tới một chỉ mục mảng cụ thể. Công thức mã băm là -

1) reverse the string.

2) keep on multiplying ascii of each character with increasing power of 31 . then add the components .

3) So hashCode() of girl would be –(ascii values of  l,r,i,g are 108, 114, 105 and 103) . 

e.g. girl =  108 * 31^0  + 114 * 31^1  + 105 * 31^2 + 103 * 31^3  = 3173020

Hash và cô gái !! Tôi biết bạn đang nghĩ gì. Sự say mê của bạn về bản song ca hoang dã đó có thể khiến bạn bỏ lỡ một điều quan trọng.

Tại sao java lại nhân nó với 31?

Đó là bởi vì, 31 là một số nguyên tố lẻ có dạng 2 ^ 5 - 1. Và số nguyên tố lẻ làm giảm cơ hội Hash Collision

Bây giờ làm thế nào mã băm này được ánh xạ tới một chỉ mục mảng?

câu trả lời là Hash Code % (Array length -1) ,. Vì vậy, “girl”được ánh xạ đến (3173020 % 3) = 1trong trường hợp của chúng tôi. là phần tử thứ hai của mảng.

và giá trị “ahhan” được lưu trữ trong LinkedList được liên kết với chỉ mục mảng 1.

HashCollision - Nếu bạn cố gắng tìm hasHCodecác khóa “misused”“horsemints”sử dụng các công thức được mô tả ở trên, bạn sẽ thấy cả hai đều cho chúng ta như nhau 1069518484. Chàaaa !! bài học kinh nghiệm -

2 đối tượng bằng nhau phải có cùng Mã băm nhưng không có gì đảm bảo nếu Mã băm khớp thì các đối tượng bằng nhau. Vì vậy, nó sẽ lưu trữ cả hai giá trị tương ứng với "lạm dụng" và "đuôi ngựa" vào nhóm 1 (1069518484% 3).

Bây giờ bản đồ băm trông giống như -

Array Index 0 –
Array Index 1 - LinkedIst (“ahhan” , “Manmohan Singh” , “guess what”)
Array Index 2 – LinkedList (“way”)
Array Index 3

Bây giờ nếu một số nội dung cố gắng tìm giá trị cho khóa “horsemints”, java sẽ nhanh chóng tìm thấy Mã băm của nó, mô-đun nó và bắt đầu tìm kiếm giá trị của nó trong LinkedList tương ứng index 1. Vì vậy, theo cách này, chúng ta không cần tìm kiếm tất cả 4 chỉ mục mảng do đó làm cho việc truy cập dữ liệu nhanh hơn.

Nhưng, chờ đã, một giây. có 3 giá trị trong chỉ mục Mảng tương ứng 1 của linkedList, làm thế nào nó phát hiện ra giá trị nào là giá trị cho khóa “đuôi ngựa”?

Thực ra tôi đã nói dối, khi tôi nói HashMap chỉ lưu trữ các giá trị trong LinkedList.

Nó lưu trữ cả cặp giá trị khóa dưới dạng mục nhập bản đồ. Vì vậy, thực tế Bản đồ trông như thế này.

Array Index 0 –
Array Index 1 - LinkedIst (<”girl” => “ahhan”> , <” misused” => “Manmohan Singh”> , <”horsemints” => “guess what”>)
Array Index 2 – LinkedList (<”no” => “way”>)
Array Index 3

Bây giờ bạn có thể thấy Trong khi duyệt qua linkedList tương ứng với ArrayIndex1, nó thực sự so sánh khóa của từng mục nhập của LinkedList đó với “đuôi ngựa” và khi tìm thấy một khóa, nó chỉ trả về giá trị của nó.

Hy vọng bạn đã vui vẻ khi đọc nó :)


1
Tôi nghĩ rằng điều này là sai: "Nó lưu trữ các khóa trong một mảng và các giá trị trong một LinkedList."
ACV

mỗi phần tử trong danh sách cho mỗi nhóm chứa khóa và giá trị cũng như tham chiếu đến nút tiếp theo.
ACV

0

Như người ta nói, một bức tranh có giá trị bằng 1000 từ. Tôi nói: một số mã tốt hơn 1000 từ. Đây là mã nguồn của HashMap. Nhận phương pháp:

/**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

Vì vậy, rõ ràng là băm được sử dụng để tìm "nhóm" và phần tử đầu tiên luôn được kiểm tra trong nhóm đó. Nếu không, thì equalscủa khóa được sử dụng để tìm phần tử thực trong danh sách liên kết.

Hãy xem put()phương pháp:

  /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

Nó phức tạp hơn một chút, nhưng rõ ràng là phần tử mới được đưa vào tab ở vị trí được tính toán dựa trên băm:

i = (n - 1) & hashđây ilà chỉ mục nơi phần tử mới sẽ được đặt (hoặc nó là "thùng"). nlà kích thước của tabmảng (mảng "nhóm").

Đầu tiên, nó được cố gắng đặt làm phần tử đầu tiên của "thùng" đó. Nếu đã có một phần tử, thì hãy thêm một nút mới vào danh sách.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.