Java TreeMap vs HashMap

1. Uvod

U ovom ćemo članku usporediti dva Karta implementacije: TreeMap i HashMap.

Obje implementacije čine sastavni dio Jave Zbirke Okvir i pohraniti podatke kao ključ-vrijednost parovi.

2. Razlike

2.1. Provedba

Prvo ćemo razgovarati o HashMap što je provedba zasnovana na raspršivanju. Proširuje SažetakKarta razred i provodi Karta sučelje. A HashMap radi na principu raspršivanje.

Ovaj Karta provedba obično djeluje kao skup hash tablica, ali kad kante postanu prevelike, transformiraju se u čvorove Čvorovi stabla, svaki strukturiran slično onima u java.util.TreeMap.

Više možete pronaći na HashMap-a unutarnji dijelovi u članku usredotočeni na to.

S druge strane, TreeMap proteže se SažetakKarta razred i provodi NavigableMap sučelje. A TreeMap pohranjuje elemente karte u a Crveno-crno drvo, koje je a Samobalansiranje Binarno stablo pretraživanja.

A, također možete pronaći više na Karte stabala unutarnji dijelovi članka usredotočeni na to ovdje.

2.2. Narudžba

HashMap ne daje nikakvo jamstvo za način na koji su elementi raspoređeni u Karta.

To znači, ne možemo pretpostaviti nikakav redoslijed dok se ponavljamo tipke i vrijednosti od a 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)); }

Međutim, stavke u a TreeMap jesu poredane prema njihovom prirodnom redoslijedu.

Ako TreeMap predmeti se ne mogu sortirati prema prirodnom redoslijedu, tada možemo koristiti a Usporednik ili Usporedive kako bi se definirao redoslijed po kojem su elementi raspoređeni u Karta:

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

2.3. Nula Vrijednosti

HashMap omogućuje pohranu najviše jednog nullključ i mnogi null vrijednosti.

Pogledajmo primjer:

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

Međutim, TreeMap ne dopušta a nullključ ali može sadržavati mnogo null vrijednosti.

A null tipka nije dopuštena jer compareTo () ili usporedi () metoda baca a NullPointerException:

@Test (očekuje se = NullPointerException.class) javna praznina whenInsertNullInTreeMap_thenException () {Map treemap = new TreeMap (); treemap.put (null, "NullPointerException"); }

Ako koristimo TreeMap s korisničkim definiranim Usporednik, onda to ovisi o provedbi usporedbe() metoda kako null vrijednosti se obrađuju.

3. Analiza izvedbe

Izvedba je najkritičnija metrika koja nam pomaže razumjeti prikladnost strukture podataka s obzirom na slučaj upotrebe.

U ovom ćemo odjeljku pružiti sveobuhvatnu analizu performansi za HashMap i TreeMap.

3.1. HashMap

HashMap, Budući da je implementirana na temelju hashtable, interno koristi strukturu podataka temeljenu na nizu kako bi organizirala svoje elemente prema hash funkcija.

HashMap pruža očekivane konstantne performanse O (1) za većinu operacija poput dodati(), ukloniti() i sadrži (). Stoga je znatno brži od a TreeMap.

Prosječno vrijeme pretraživanja elementa pod razumnom pretpostavkom u hash tablici je O (1). Ali, nepravilna provedba hash funkcija može dovesti do loše raspodjele vrijednosti u segmentima što rezultira:

  • Memory Overhead - mnogi segmenti ostaju neiskorišteni
  • Pogoršanje performansišto je veći broj sudara, to su niže performanse

Prije Jave 8, Odvojeno ulančavanje bio jedini preferirani način rješavanja sudara. Obično se provodi pomoću povezanih popisa, tj., ako dođe do sudara ili ako dva različita elementa imaju istu raspršenu vrijednost, pohranite obje stavke na isti povezani popis.

Stoga je traženje elementa u a HashMap, u najgorem slučaju moglo je potrajati sve dok je traženje elementa na povezanom popisu tj.Na) vrijeme.

Međutim, s pojavom JEP-a 180, došlo je do suptilne promjene u provedbi načina rasporeda elemenata u HashMap.

Prema specifikaciji, kad segmenti postanu preveliki i sadrže dovoljno čvorova, transformiraju se u načine rada Čvorovi stabla, svaki strukturiran slično onima u TreeMap.

Stoga će se u slučaju sudara s velikim raspršivanjem poboljšati performanse u najgorem slučaju Na) do O (log n).

Kôd koji izvodi ovu transformaciju ilustriran je u nastavku:

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

Vrijednost za TREEIFY_THRESHOLD je osam što učinkovito označava broj pragova za korištenje stabla, a ne povezani popis za skup.

Očito je da:

  • A HashMap zahtijeva više memorije nego što je potrebno za čuvanje podataka
  • A HashMap ne smije biti puna više od 70% - 75%. Ako se približi, promijeniće se veličina i unosi se promijene
  • Preispitivanje zahtijeva n skupe operacije pri čemu naš stalni vremenski umetak postaje redovan Na)
  • Algoritam raspršivanja određuje redoslijed umetanja objekata u HashMap

Izvedba a HashMap može se podesiti postavljanjem prilagođenih početni kapacitet i faktor opterećenja, u vrijeme HashMap samo stvaranje objekta.

Međutim, trebali bismo odabrati a HashMap ako:

  • otprilike znamo koliko predmeta treba održavati u našoj kolekciji
  • ne želimo vaditi predmete u prirodnom redoslijedu

Pod gornjim okolnostima, HashMap je naš najbolji izbor jer nudi stalno umetanje, pretraživanje i brisanje vremena.

3.2. TreeMap

A TreeMap pohranjuje svoje podatke u hijerarhijsko stablo s mogućnošću sortiranja elemenata uz pomoć prilagođenog Usporednik.

Sažetak njegove izvedbe:

  • TreeMap pruža izvedbu od O (log (n)) za većinu operacija poput dodati(), ukloniti() i sadrži ()
  • A Karta drveća može uštedjeti memoriju (u usporedbi s HashMap) jer koristi samo onu količinu memorije koja je potrebna za držanje njegovih predmeta, za razliku od a HashMap koja koristi susjedno područje pamćenja
  • Stablo bi trebalo održavati ravnotežu kako bi održalo predviđenu izvedbu, to zahtijeva znatan napor, što otežava provedbu

Trebali bismo ići na a TreeMap kad god:

  • moraju se uzeti u obzir ograničenja pamćenja
  • ne znamo koliko predmeta treba pohraniti u memoriju
  • želimo izvući predmete u prirodnom redoslijedu
  • ako će se stavke dosljedno dodavati i uklanjati
  • spremni smo prihvatiti O (zapisnik n) vrijeme pretraživanja

4. Sličnosti

4.1. Jedinstveni elementi

Oba TreeMap i HashMap ne podržavaju duplicirane ključeve. Ako se doda, poništava prethodni element (bez pogreške ili iznimke):

@Test javna praznina givenHashMapAndTreeMap_whenputDuplicates_thenOnlyUnique () {Map treeMap = new HashMap (); treeMap.put (1, "Baeldung"); treeMap.put (1, "Baeldung"); assertTrue (treeMap.size () == 1); Karta treeMap2 = nova TreeMap (); treeMap2.put (1, "Baeldung"); treeMap2.put (1, "Baeldung"); assertTrue (treeMap2.size () == 1); }

4.2. Istodobni pristup

Oba Karta implementacije nisu sinkronizirano i istodobnim pristupom moramo upravljati samostalno.

Obje se moraju sinkronizirati izvana kad god im istovremeno pristupa više niti i barem ih jedna od niti modificira.

Moramo izričito koristiti Collections.synchronizedMap (mapName) da biste dobili sinkronizirani prikaz dane karte.

4.3. Fail-Fast Iterators

The Iterator baca a ConcurrentModificationException ako je Karta mijenja se na bilo koji način iu bilo koje vrijeme nakon izrade iteratora.

Osim toga, možemo koristiti metodu uklanjanja iteratora da bismo promijenili Karta tijekom iteracije.

Pogledajmo primjer:

@Test public void whenModifyMapDuringIteration_thenThrowExecption () {Map hashmap = new HashMap (); hashmap.put (1, "Jedan"); hashmap.put (2, "Dva"); Izvršna izvršna datoteka = () -> hashmap .forEach ((ključ, vrijednost) -> hashmap.remove (1)); assertThrows (ConcurrentModificationException.class, izvršna datoteka); }

5. Koju implementaciju koristiti?

Općenito, obje implementacije imaju svoje prednosti i nedostatke, međutim, radi se o razumijevanju temeljnih očekivanja i zahtjeva koji moraju upravljati našim izborom u vezi s istim.

Rezimirajući:

  • Trebali bismo koristiti a TreeMap ako želimo da naši unosi budu sortirani
  • Trebali bismo koristiti a HashMap ako prednost dajemo izvedbi nad potrošnjom memorije
  • Budući da je a TreeMap ima značajnije mjesto, mogli bismo ga razmotriti ako želimo pristupiti objektima koji su relativno blizu jedan drugome prema njihovom prirodnom rasporedu
  • HashMap može se podesiti pomoću početni kapacitet i loadFactor, što nije moguće za TreeMap
  • Možemo koristiti LinkedHashMap ako želimo sačuvati redoslijed umetanja dok imamo koristi od stalnog vremenskog pristupa

6. Zaključak

U ovom smo članku pokazali razlike i sličnosti između TreeMap i HashMap.

Kao i uvijek, primjeri koda za ovaj članak dostupni su na GitHubu.