Middle
Πώς είναι δομημένη και οργανωμένη η δομή δεδομένων HashMap στη Java;
sobes.tech AI
Απάντηση από AI
Το HashMap στη Java βασίζεται στις αρχές της κατακερματισμού. Αποθηκεύει ζεύγη "κλειδί-τιμή".
Εσωτερική δομή:
- Πίνακες κάδων (δοχείων). Κάθε κάδος είναι μια συνδεδεμένη λίστα (ή δέντρο, από το Java 8 και μετά, όταν υπάρχουν πολλές συγκρούσεις).
- Όταν προστίθεται ένα στοιχείο (
put):- Υπολογίζεται ο κωδικός κατακερματισμού του κλειδιού (
key.hashCode()). - Ο κωδικός κατακερματισμού τροποποιείται για καλύτερη κατανομή (
hash). - Χρησιμοποιώντας τον τροποποιημένο κωδικό και το μέγεθος του πίνακα, υπολογίζεται η θέση του κάδου (
hash & (array_size - 1)). - Το στοιχείο (ζεύγος "κλειδί-τιμή" ως αντικείμενο
Node) τοποθετείται σε αυτόν τον κάδο. Αν ο κάδος ήδη περιέχει στοιχεία, το νέο στοιχείο προστίθεται στην αρχή της συνδεδεμένης λίστας ή του δέντρου. - Κατά την προσθήκη, ελέγχεται αν το κλειδί υπάρχει ήδη: χρησιμοποιείται η μέθοδος
equals()για σύγκριση κλειδιών. Αν βρεθεί, η τιμή ενημερώνεται.
- Υπολογίζεται ο κωδικός κατακερματισμού του κλειδιού (
- Όταν λαμβάνεται ένα στοιχείο (
get):- Υπολογίζεται η θέση του κάδου με το κλειδί.
- Στον κάδο, γίνεται αναζήτηση του στοιχείου με το κλειδί, χρησιμοποιώντας τις μεθόδους
hashCode()καιequals(). - Επιστρέφεται η σχετική τιμή.
Οργάνωση:
- Συγκρούσεις: Αν πολλά κλειδιά έχουν τον ίδιο κωδικό κατακερματισμού και πέφτουν στον ίδιο κάδο, αποθηκεύονται ως συνδεδεμένη λίστα. Από το Java 8 και μετά, αν ο αριθμός των στοιχείων σε έναν κάδο ξεπεράσει ένα όριο (συνήθως 8), η λίστα μετατρέπεται σε δέντρο για ταχύτερη αναζήτηση (O(log n) αντί για O(n)).
- Αναπλήρωση μεγέθους: Όταν ο αριθμός των στοιχείων ξεπεράσει το όριο φόρτωσης (
load factor * capacity), το HashMap αυξάνει το μέγεθος του εσωτερικού πίνακα και επανακατακερματίζει όλα τα στοιχεία. Αυτή η λειτουργία είναι ακριβή (O(n)). - Παράμετροι:
capacity: Αρχικό μέγεθος του πίνακα (προεπιλογή 16).load factor: Όριο φόρτωσης (προεπιλογή 0.75). Ορίζει πότε θα γίνει αναπλήρωση.
Γιατί είναι σημαντικά τα hashCode() και equals():
- Η σωστή λειτουργία του HashMap εξαρτάται από την σωστή υλοποίηση αυτών των μεθόδων.
- Αν το
equals()επιστρέφειtrueγια δύο αντικείμενα, τοhashCode()πρέπει να επιστρέφει την ίδια τιμή. - Λανθασμένη υλοποίηση μπορεί να οδηγήσει σε μη εύρεση στοιχείων (
getθα επιστρέψειnull), ακόμα και αν έχουν προστεθεί.
Παράδειγμα δομής Node:
// Μόνο εσωτερική χρήση
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
Το HashMap παρέχει γρήγορη (μέσος όρος O(1)) προσθήκη, λήψη και διαγραφή στοιχείων με ομοιόμορφη κατανομή των hash. Στην χειρότερη περίπτωση (ισχυρές συγκρούσεις), η λειτουργία μπορεί να γίνει O(n) ή O(log n) με δέντρα.