Java TreeMap vs HashMap

1. Pengenalan

Dalam artikel ini, kita akan membandingkan dua implementasi Peta : TreeMap dan HashMap .

Kedua-dua implementasi ini merupakan bagian yang tidak terpisahkan dari Java Collections Framework dan menyimpan data sebagai pasangan nilai-kunci .

2. Perbezaan

2.1. Pelaksanaan

Pertama-tama kita akan membincangkan mengenai HashMap yang merupakan pelaksanaan berasaskan hashtable. Ia meluaskan kelas AbstractMap dan menerapkan antara muka Peta . A HashMap berfungsi pada prinsip hashing .

Ini Peta pelaksanaan biasanya bertindak sebagai bucketed jadual hash , tetapi apabila baldi terlalu besar, mereka akan berubah menjadi nod TreeNodes , setiap berstruktur sama dengan orang-orang di java.util.TreeMap.

Anda boleh mendapatkan lebih banyak maklumat mengenai dalaman HashMap dalam artikel yang difokuskan padanya.

Sebaliknya, TreeMap memperluas kelas AbstractMap dan melaksanakan antara muka NavigableMap . A TreeMap Kedai peta unsur dalam Red-hitam pokok, yang merupakan Self-Balancing Binary Cari Tree .

Anda juga boleh mendapatkan lebih banyak maklumat mengenai dalaman TreeMap dalam artikel yang difokuskan di sini.

2.2. Pesanan

HashMap tidak memberikan jaminan mengenai cara elemen disusun dalam Peta .

Ini bermakna, kita tidak boleh menganggap apa-apa perintah sementara iterating lebih kunci dan nilai-nilai yang HashMap :

@Test public void whenInsertObjectsHashMap_thenRandomOrder() { Map hashmap = new HashMap(); hashmap.put(3, "TreeMap"); hashmap.put(2, "vs"); hashmap.put(1, "HashMap"); assertThat(hashmap.keySet(), containsInAnyOrder(1, 2, 3)); }

Walau bagaimanapun, item dalam TreeMap sedang disusun mengikut perintah semula jadi mereka .

Sekiranya objek TreeMap tidak dapat diurutkan mengikut urutan semula jadi maka kita dapat menggunakan Comparator atau Comparable untuk menentukan susunan elemen yang disusun dalam Peta:

@Test public void whenInsertObjectsTreeMap_thenNaturalOrder() { Map treemap = new TreeMap(); treemap.put(3, "TreeMap"); treemap.put(2, "vs"); treemap.put(1, "HashMap"); assertThat(treemap.keySet(), contains(1, 2, 3)); }

2.3. Nilai Batal

HashMap membolehkan menyimpan paling banyak satu kunci kosong dan banyak nilai kosong .

Mari lihat contoh:

@Test public void whenInsertNullInHashMap_thenInsertsNull() { Map hashmap = new HashMap(); hashmap.put(null, null); assertNull(hashmap.get(null)); }

Walau bagaimanapun, TreeMap tidak membenarkan kunci kosong tetapi mungkin mengandungi banyak nilai nol .

A null utama tidak dibenarkan kerana compareTo () atau membandingkan () kaedah melemparkan NullPointerException:

@Test(expected = NullPointerException.class) public void whenInsertNullInTreeMap_thenException() { Map treemap = new TreeMap(); treemap.put(null, "NullPointerException"); }

Sekiranya kita menggunakan TreeMap dengan Comparator yang ditentukan pengguna , maka ia bergantung pada pelaksanaan kaedah membandingkan () bagaimana nilai null ditangani.

3. Analisis Prestasi

Prestasi adalah metrik yang paling kritikal yang membantu kita memahami kesesuaian struktur data yang diberi kes penggunaan.

Di bahagian ini, kami akan memberikan analisis prestasi menyeluruh untuk HashMap dan TreeMap.

3.1. Peta Hash

HashMap, sebagai implementasi berdasarkan hashtable, secara dalaman menggunakan struktur data berdasarkan array untuk mengatur elemennya sesuai dengan fungsi hash .

HashMap memberikan jangkaan prestasi masa tetap O (1) untuk kebanyakan operasi seperti tambah () , hapus () dan mengandungi (). Oleh itu, ia jauh lebih pantas daripada TreeMap .

Purata masa untuk mencari elemen di bawah anggapan yang munasabah, dalam jadual hash adalah O (1). Tetapi, pelaksanaan fungsi hash yang tidak betul dapat menyebabkan pengedaran nilai yang buruk dalam baldi yang mengakibatkan:

  • Overhead Memori - banyak baldi tetap tidak digunakan
  • Kemerosotan Prestasi - semakin tinggi jumlah perlanggaran, semakin rendah prestasi

Sebelum Java 8, Berasingan Berantai adalah satu-satunya cara yang disukai untuk menangani perlanggaran. Ia biasanya dilaksanakan menggunakan senarai terpaut, iaitu , jika ada perlanggaran atau dua elemen yang berbeza mempunyai nilai hash yang sama maka simpan kedua-dua item tersebut dalam senarai yang sama.

Oleh itu, mencari elemen dalam HashMap, dalam keadaan terburuk boleh dilakukan selama mencari elemen dalam senarai terpaut iaitu waktu O (n) .

Namun, dengan JEP 180 masuk ke dalam gambar, ada perubahan halus dalam pelaksanaan cara elemen disusun dalam HashMap.

Menurut spesifikasi, apabila baldi terlalu besar dan mengandungi simpul yang cukup, mereka berubah menjadi mod TreeNodes , masing-masing tersusun serupa dengan yang ada di TreeMap .

Oleh itu, sekiranya berlaku perlanggaran hash tinggi, prestasi terburuk akan bertambah baik dari O (n) ke O (log n).

Kod yang melakukan transformasi ini telah digambarkan di bawah:

if(binCount >= TREEIFY_THRESHOLD - 1) { treeifyBin(tab, hash); }

Nilai untuk TREEIFY_THRESHOLD adalah lapan yang secara berkesan menunjukkan jumlah ambang untuk menggunakan pokok dan bukannya senarai terpaut untuk baldi.

Sudah terbukti bahawa:

  • A HashMap memerlukan cara yang lebih ingatan daripada yang diperlukan untuk memegang data
  • A HashMap tidak boleh lebih daripada 70% - 75% penuh. Sekiranya ia hampir, ia akan diubah saiznya dan entri diulang
  • Pemulihan semula memerlukan operasi n yang mahal di mana sisipan masa tetap kita menjadi tertib O (n)
  • Algoritma hash inilah yang menentukan urutan memasukkan objek dalam HashMap

The performance of a HashMap can be tuned by setting the custom initial capacity and the load factor, at the time of HashMap object creation itself.

However, we should choose a HashMap if:

  • we know approximately how many items to maintain in our collection
  • we don't want to extract items in a natural order

Under the above circumstances, HashMap is our best choice because it offers constant time insertion, search, and deletion.

3.2. TreeMap

A TreeMap stores its data in a hierarchical tree with the ability to sort the elements with the help of a custom Comparator.

A summary of its performance:

  • TreeMap provides a performance of O(log(n)) for most operations like add(), remove() and contains()
  • A Treemap can save memory (in comparison to HashMap) because it only uses the amount of memory needed to hold its items, unlike a HashMap which uses contiguous region of memory
  • A tree should maintain its balance in order to keep its intended performance, this requires a considerable amount of effort, hence complicates the implementation

We should go for a TreeMap whenever:

  • memory limitations have to be taken into consideration
  • we don't know how many items have to be stored in memory
  • we want to extract objects in a natural order
  • if items will be consistently added and removed
  • we're willing to accept O(log n) search time

4. Similarities

4.1. Unique Elements

Both TreeMap and HashMap don't support duplicate keys. If added, it overrides the previous element (without an error or an exception):

@Test public void givenHashMapAndTreeMap_whenputDuplicates_thenOnlyUnique() { Map treeMap = new HashMap(); treeMap.put(1, "Baeldung"); treeMap.put(1, "Baeldung"); assertTrue(treeMap.size() == 1); Map treeMap2 = new TreeMap(); treeMap2.put(1, "Baeldung"); treeMap2.put(1, "Baeldung"); assertTrue(treeMap2.size() == 1); }

4.2. Concurrent Access

Both Map implementations aren't synchronized and we need to manage concurrent access on our own.

Both must be synchronized externally whenever multiple threads access them concurrently and at least one of the threads modifies them.

We have to explicitly use Collections.synchronizedMap(mapName) to obtain a synchronized view of a provided map.

4.3. Fail-Fast Iterators

The Iterator throws a ConcurrentModificationException if the Map gets modified in any way and at any time once the iterator has been created.

Additionally, we can use the iterator’s remove method to alter the Map during iteration.

Let's see an example:

@Test public void whenModifyMapDuringIteration_thenThrowExecption() { Map hashmap = new HashMap(); hashmap.put(1, "One"); hashmap.put(2, "Two"); Executable executable = () -> hashmap .forEach((key,value) -> hashmap.remove(1)); assertThrows(ConcurrentModificationException.class, executable); }

5. Which Implementation to Use?

In general, both implementations have their respective pros and cons, however, it's about understanding the underlying expectation and requirement which must govern our choice regarding the same.

Summarizing:

  • We should use a TreeMap if we want to keep our entries sorted
  • We should use a HashMap if we prioritize performance over memory consumption
  • Oleh kerana TreeMap mempunyai lokasi yang lebih ketara, kami mungkin mempertimbangkannya jika kami ingin mengakses objek yang agak dekat satu sama lain mengikut susunan semula jadi mereka
  • HashMap dapat diselaraskan dengan menggunakan Kapasitas awal dan loadFactor , yang tidak mungkin dilakukan untuk TreeMap
  • Kami dapat menggunakan LinkedHashMap jika kami ingin mengekalkan pesanan penyisipan sambil memanfaatkan akses masa yang tetap

6. Kesimpulannya

Dalam artikel ini, kami menunjukkan perbezaan dan persamaan antara TreeMap dan HashMap .

Seperti biasa, contoh kod untuk artikel ini terdapat di GitHub.