Sesuai dokumen tautan berikut: Implementasi Java HashMap
Saya bingung dengan implementasi HashMap
(atau lebih tepatnya, peningkatan dalam HashMap
). Pertanyaan saya adalah:
pertama
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
Mengapa dan bagaimana konstanta ini digunakan? Saya ingin beberapa contoh yang jelas untuk ini. Bagaimana mereka mencapai perolehan kinerja dengan ini?
Kedua
Jika Anda melihat kode sumber HashMap
di JDK, Anda akan menemukan kelas dalam statis berikut:
static final class TreeNode<K, V> extends java.util.LinkedHashMap.Entry<K, V> {
HashMap.TreeNode<K, V> parent;
HashMap.TreeNode<K, V> left;
HashMap.TreeNode<K, V> right;
HashMap.TreeNode<K, V> prev;
boolean red;
TreeNode(int arg0, K arg1, V arg2, HashMap.Node<K, V> arg3) {
super(arg0, arg1, arg2, arg3);
}
final HashMap.TreeNode<K, V> root() {
HashMap.TreeNode arg0 = this;
while (true) {
HashMap.TreeNode arg1 = arg0.parent;
if (arg0.parent == null) {
return arg0;
}
arg0 = arg1;
}
}
//...
}
Bagaimana cara menggunakannya? Saya hanya ingin penjelasan tentang algoritmanya .
sumber
String
, memiliki ruang nilai yang jauh lebih besar daripadaint
kode hash, oleh karena itu, tabrakan tidak dapat dihindari. Sekarang tergantung pada nilai aktual, seperti nilai aktualString
, yang Anda masukkan ke dalam peta, apakah Anda mendapatkan distribusi yang merata atau tidak. Distribusi yang buruk bisa jadi hanya karena kesialan.java.lang.String
memiliki deterministik, non-kriptografikhashCode
, sehingga penyerang dapat dengan mudah membuat String berbeda dengan kode hash yang bertabrakan. Sebelum pengoptimalan ini, ini dapat menurunkan operasi HashMap menjadi O (n) -time, sekarang hanya menurunkannya menjadi O (log (n)).if the objects implement that interface, else the identity hash code.
saya sedang mencari bagian lain ini.MIN_TREEIFY_CAPACITY
. Apakah ini berarti "Setelah kita memasukkan kunci yang akan di-hash ke keranjang yang sudah berisi 8 (TREEIFY_THRESHOLD
) kunci dan jika sudah ada 64 (MIN_TREEIFY_CAPACITY
) kunciHashMap
, daftar tertaut dari keranjang itu akan diubah menjadi pohon yang seimbang."Untuk membuatnya lebih sederhana (sebanyak yang saya bisa lebih sederhana) + beberapa detail lainnya.
Properti ini bergantung pada banyak hal internal yang akan sangat keren untuk dipahami - sebelum berpindah ke properti tersebut secara langsung.
TREEIFY_THRESHOLD -> ketika satu keranjang mencapai ini (dan jumlahnya melebihi
MIN_TREEIFY_CAPACITY
), itu diubah menjadi simpul pohon merah / hitam yang seimbang sempurna . Mengapa? Karena kecepatan pencarian. Pikirkan dengan cara yang berbeda:Beberapa intro untuk topik berikutnya. Mengapa jumlah nampan / ember selalu pangkat dua ? Setidaknya dua alasan: lebih cepat dari operasi modulo dan modulo pada bilangan negatif akan menjadi negatif. Dan Anda tidak dapat memasukkan Entri ke dalam keranjang "negatif":
int arrayIndex = hashCode % buckets; // will be negative buckets[arrayIndex] = Entry; // obviously will fail
Sebaliknya, ada trik bagus yang digunakan sebagai pengganti modulo:
(n - 1) & hash // n is the number of bins, hash - is the hash function of the key
Itu secara semantik sama dengan operasi modulo. Ini akan menjaga bit yang lebih rendah. Ini memiliki konsekuensi yang menarik ketika Anda melakukannya:
Map<String, String> map = new HashMap<>();
Di sinilah mengalikan ember berperan. Dalam kondisi tertentu (akan membutuhkan banyak waktu untuk menjelaskan dengan detail yang tepat ), ukuran bucket menjadi dua kali lipat. Mengapa? Ketika ukuran ember digandakan, ada satu bit lagi yang ikut bermain .
Karena itu, proses ini disebut hashing ulang. Ini mungkin lambat. Artinya (untuk orang yang peduli) karena HashMap "bercanda" sebagai: cepat, cepat, cepat, slooow . Ada implementasi lain - cari hashmap yang tidak dijeda ...
Sekarang UNTREEIFY_THRESHOLD mulai berfungsi setelah melakukan hashing ulang. Pada titik itu, beberapa entri mungkin berpindah dari nampan ini ke yang lain (mereka menambahkan satu bit lagi ke
(n-1)&hash
komputasi - dan dengan demikian mungkin pindah ke keranjang lain ) dan mungkin mencapai iniUNTREEIFY_THRESHOLD
. Pada titik ini, tidak ada gunanya menyimpan tempat sampah sebagaired-black tree node
, tetapi sebagaiLinkedList
gantinya, sukaMIN_TREEIFY_CAPACITY adalah jumlah minimum keranjang sebelum keranjang tertentu diubah menjadi Pohon.
sumber
TreeNode
adalah cara alternatif untuk menyimpan entri milik satu bin dari fileHashMap
. Dalam implementasi yang lebih lama, entri bin disimpan dalam daftar tertaut. Di Java 8, jika jumlah entri dalam sebuah bin melewati ambang (TREEIFY_THRESHOLD
), mereka disimpan dalam struktur pohon alih-alih daftar tertaut asli. Ini adalah pengoptimalan.Dari implementasi:
/* * Implementation notes. * * This map usually acts as a binned (bucketed) hash table, but * when bins get too large, they are transformed into bins of * TreeNodes, each structured similarly to those in * java.util.TreeMap. Most methods try to use normal bins, but * relay to TreeNode methods when applicable (simply by checking * instanceof a node). Bins of TreeNodes may be traversed and * used like any others, but additionally support faster lookup * when overpopulated. However, since the vast majority of bins in * normal use are not overpopulated, checking for existence of * tree bins may be delayed in the course of table methods.
sumber
TREEIFY_THRESHOLD
DAN jumlah totalnya paling sedikitMIN_TREEIFY_CAPACITY
. Saya sudah mencoba untuk menutupi itu dalam jawaban saya ...Anda perlu memvisualisasikannya: katakanlah ada Kunci Kelas dengan hanya fungsi hashCode () yang diganti untuk selalu mengembalikan nilai yang sama
public class Key implements Comparable<Key>{ private String name; public Key (String name){ this.name = name; } @Override public int hashCode(){ return 1; } public String keyName(){ return this.name; } public int compareTo(Key key){ //returns a +ve or -ve integer } }
dan kemudian di tempat lain, saya memasukkan 9 entri ke dalam HashMap dengan semua kunci menjadi contoh dari kelas ini. misalnya
Map<Key, String> map = new HashMap<>(); Key key1 = new Key("key1"); map.put(key1, "one"); Key key2 = new Key("key2"); map.put(key2, "two"); Key key3 = new Key("key3"); map.put(key3, "three"); Key key4 = new Key("key4"); map.put(key4, "four"); Key key5 = new Key("key5"); map.put(key5, "five"); Key key6 = new Key("key6"); map.put(key6, "six"); Key key7 = new Key("key7"); map.put(key7, "seven"); Key key8 = new Key("key8"); map.put(key8, "eight"); //Since hascode is same, all entries will land into same bucket, lets call it bucket 1. upto here all entries in bucket 1 will be arranged in LinkedList structure e.g. key1 -> key2-> key3 -> ...so on. but when I insert one more entry Key key9 = new Key("key9"); map.put(key9, "nine"); threshold value of 8 will be reached and it will rearrange bucket1 entires into Tree (red-black) structure, replacing old linked list. e.g. key1 / \ key2 key3 / \ / \
Tree traversal lebih cepat {O (log n)} daripada LinkedList {O (n)} dan saat n bertambah, perbedaannya menjadi lebih signifikan.
sumber
compareTo
dariComparable
.identityHashCode
adalah mekanisme lain yang digunakannya.Key
tidak diimplementasikanComparable
,identityHashCode
akan digunakan :)Perubahan dalam implementasi HashMap telah ditambahkan dengan JEP-180 . Tujuannya adalah untuk:
Namun kinerja murni bukanlah satu-satunya keuntungan. Ini juga akan mencegah serangan HashDoS , jika peta hash digunakan untuk menyimpan input pengguna, karena pohon merah-hitam yang digunakan untuk menyimpan data di bucket memiliki kompleksitas penyisipan kasus terburuk di O (log n). Pohon itu digunakan setelah kriteria tertentu terpenuhi - lihat jawaban Eugene .
sumber
Untuk memahami implementasi internal hashmap, Anda perlu memahami hashing. Hashing dalam bentuknya yang paling sederhana, adalah cara untuk menetapkan kode unik untuk variabel / objek apa pun setelah menerapkan rumus / algoritme apa pun pada propertinya.
Fungsi hash yang benar harus mengikuti aturan ini -
“Fungsi hash harus mengembalikan kode hash yang sama setiap kali fungsi diterapkan pada objek yang sama atau sama. Dengan kata lain, dua objek yang sama harus menghasilkan kode hash yang sama secara konsisten. "
sumber