Uvod u klasu Java.util.Hashtable

1. Pregled

Hashtable je najstarija implementacija strukture podataka hash tablice u Javi. The HashMap je druga implementacija koja je uvedena u JDK 1.2.

Obje klase pružaju sličnu funkcionalnost, ali postoje i male razlike koje ćemo istražiti u ovom vodiču.

2. Kada koristiti Hashtable

Recimo da imamo rječnik, gdje svaka riječ ima svoju definiciju. Također, moramo brzo doći, ubaciti i ukloniti riječi iz rječnika.

Stoga, Hashtable (ili HashMap) ima smisla. Riječi će biti ključevi u Hashtable, jer bi trebali biti jedinstveni. S druge strane, definicije će biti vrijednosti.

3. Primjer upotrebe

Nastavimo s primjerom rječnika. Modelirat ćemo Riječ kao ključ:

riječ javne klase {naziv privatnog niza; javna riječ (naziv niza) {this.name = ime; } // ...}

Recimo da su vrijednosti Žice. Sada možemo stvoriti Hashtable:

Tablica tablice = nova tablica tablice ();

Prvo, dodajmo unos:

Riječ riječi = nova Riječ ("mačka"); table.put (riječ, "životinja");

Također, da biste dobili unos:

Definicija niza = table.get (riječ);

Napokon, uklonimo unos:

definicija = tablica.remove (riječ);

U razredu postoji mnogo više metoda, a neke ćemo opisati kasnije.

Ali prvo, razgovarajmo o nekim zahtjevima za ključni objekt.

4. Važnost hashCode ()

Koristiti se kao ključ u a Hashtable, objekt ne smije kršiti hashCode () ugovor. Ukratko, jednaki objekti moraju vratiti isti kod. Da bismo razumjeli zašto pogledajmo kako je organizirana hash tablica.

Hashtable koristi niz. Svaka pozicija u nizu je "segment" koji može biti null ili sadržavati jedan ili više parova ključ / vrijednost. Izračunava se indeks svakog para.

Ali zašto ne pohraniti elemente uzastopno, dodajući nove elemente na kraj niza?

Poanta je u tome što je pronalaženje elementa indeksom mnogo brže nego ponavljanje elemenata s usporedbom uzastopno. Stoga nam je potrebna funkcija koja preslikava ključeve u indekse.

4.1. Tablica izravnih adresa

Najjednostavniji primjer takvog mapiranja je tablica izravnih adresa. Ovdje se ključevi koriste kao indeksi:

indeks (k) = k, gdje je k ključ

Ključevi su jedinstveni, tj. Svaki segment sadrži jedan par ključ / vrijednost. Ova tehnika dobro funkcionira za cjelobrojne tipke kad je mogući raspon njih relativno mali.

Ali ovdje imamo dva problema:

  • Prvo, naši ključevi nisu cijeli brojevi, ali Riječ predmeta
  • Drugo, da su cjelobrojni, nitko ne bi jamčio da su mali. Zamislite da su tipke 1, 2 i 1000000. Imat ćemo velik niz veličine 1000000 sa samo tri elementa, a ostatak će biti izgubljeni prostor

hashCode () metoda rješava prvi problem.

Logika za manipulaciju podacima u sustavu Hashtable rješava drugi problem.

Razmotrimo ovo detaljno.

4.2. hashCode () Metoda

Bilo koji Java objekt nasljeđuje hashCode () metoda koja vraća an int vrijednost. Ova se vrijednost izračunava iz adrese interne memorije objekta. Prema zadanim postavkama hashCode () vraća različite cjeline za različite objekte.

Tako bilo koji ključni objekt može se pretvoriti u cijeli broj pomoću hashCode (). Ali ovaj je cijeli broj možda velik.

4.3. Smanjivanje dometa

dobiti(), staviti() i ukloniti() metode sadrže kod koji rješava drugi problem - smanjenje opsega mogućih cijelih brojeva.

Formula izračunava indeks za ključ:

int indeks = (hash & 0x7FFFFFFF)% tab.length;

Gdje tab.duljina je veličina polja i hash je broj koji su vratili ključevi hashCode () metoda.

Kao što možemo vidjeti indeks je podsjetnik na podjelu hash po veličini polja. Imajte na umu da jednaki hash kodovi proizvode isti indeks.

4.4. Sudari

Nadalje, čak različiti hash kodovi mogu stvoriti isti indeks. To nazivamo sudarom. Za rješavanje sudara Hashtable pohranjuje a LinkedList parova ključ / vrijednost.

Takva struktura podataka naziva se hash tablica s lančanim ulančavanjem.

4.5. Faktor opterećenja

Lako je pogoditi da sudari usporavaju operacije s elementima. Da bismo dobili unos, nije dovoljno znati njegov indeks, već moramo proći kroz popis i izvršiti usporedbu sa svakom stavkom.

Stoga je važno smanjiti broj sudara. Što je veći niz, to je manja šansa za sudar. Faktor opterećenja određuje ravnotežu između veličine polja i performansi. Prema zadanim postavkama iznosi 0,75, što znači da se veličina polja udvostručuje kad 75% segmenata ne postane prazno. Ovu operaciju izvršava ponoviti() metoda.

No, vratimo se ključevima.

4.6. Nadjačavanje equals () i hashCode ()

Kada stavimo unos u Hashtable i izvučemo ga iz toga, očekujemo da se vrijednost može dobiti ne samo istim primjerkom ključa već i jednakim ključem:

Riječ riječi = nova Riječ ("mačka"); table.put (riječ, "životinja"); Izvučen je niz = table.get (nova Word ("mačka"));

Da bismo postavili pravila jednakosti, nadjačavamo ključeve jednako () metoda:

javna logička vrijednost jednako (Objekt o) {if (o == ovo) return true; if (! (o instanceof Word)) return false; Riječ riječi = (Riječ) o; vrati word.getName (). jednako (this.name); }

Ali ako ne nadjačamo hashCode () prilikom nadjačavanja jednako () tada dva jednaka ključa mogu završiti u različitim kantama jer Hashtable izračunava indeks ključa pomoću svog hash koda.

Pogledajmo izbliza gornji primjer. Što se događa ako ne poništimo hashCode ()?

  • Dva slučaja Riječ su ovdje uključeni - prvi je za stavljanje unosa, a drugi za dobivanje unosa. Iako su ove instance jednake, njihove hashCode () metoda vraća različite brojeve
  • Indeks za svaki ključ izračunava se prema formuli iz odjeljka 4.3. Prema ovoj formuli, različiti hash kodovi mogu proizvesti različite indekse
  • To znači da stavimo unos u jednu kantu, a zatim ga pokušamo izvaditi iz druge kante. Takva se logika lomi Hashtable

Jednaki ključevi moraju vraćati jednake hash kodove, zato mi nadjačavamo hashCode () metoda:

javni int hashCode () {return name.hashCode (); }

Imajte na umu da Također se preporučuje da nejednaki ključevi vraćaju različite hash kodove, inače završe u istoj kanti. Ovo će pogoditi izvedbu, dakle, izgubiti neke prednosti a Hashtable.

Također, imajte na umu da nas nije briga za tipke Niz, Cijeli broj, Dugo ili drugi tip omota. Oba jednak() i hashCode () metode su već zamijenjene u klasama omotača.

5. Iteracija Hashtables

Postoji nekoliko načina za ponavljanje Hashtables. U ovom odjeljku dobro razgovarajte o njima i objasnite neke implikacije.

5.1. Fail Fast: Iteracija

Neuspješna iteracija znači da ako a Hashtable je izmijenjen nakon svog Iterator je stvoren, a zatim ConcurrentModificationException bit će bačen. Pokažimo to.

Prvo ćemo stvoriti Hashtable i dodajte u njega unose:

Tablica tablice = nova tablica tablice (); table.put (nova Word ("mačka"), "životinja"); table.put (nova Word ("pas"), "druga životinja");

Drugo, stvorit ćemo Iterator:

Iterator it = table.keySet (). Iterator ();

I treće, izmijenit ćemo tablicu:

table.remove (nova Word ("pas"));

Ako pokušamo prelistati tablicu, dobit ćemo a ConcurrentModificationException:

while (it.hasNext ()) {Ključ riječi = it.next (); }
java.util.ConcurrentModificationException na java.util.Hashtable $ Enumerator.next (Hashtable.java:1378)

ConcurrentModificationException pomaže u pronalaženju programskih pogrešaka i na taj način izbjegava nepredvidivo ponašanje, kada se, na primjer, jedna nit ponavlja kroz tablicu, a druga je pokušava istodobno modificirati.

5.2. Ne uspije brzo: Nabrajanje

Nabrajanje u Hashtable nije neuspješan. Pogledajmo primjer.

Prvo, izradimo a Hashtable i dodajte u njega unose:

Tablica tablice = nova tablica tablice (); table.put (nova Word ("1"), "jedan"); table.put (nova Word ("2"), "dva");

Drugo, stvorimo Nabrajanje:

Nabrajanje enumKey = table.keys ();

Treće, izmijenimo tablicu:

table.remove (nova Word ("1"));

Sada, ako prođemo kroz tablicu, to neće izuzeti:

while (enumKey.hasMoreElements ()) {Ključ riječi = enumKey.nextElement (); }

5.3. Nepredvidivi redoslijed ponavljanja

Također imajte na umu da je redoslijed ponavljanja u a Hashtable je nepredvidljiv i ne odgovara redoslijedu u kojem su dodani unosi.

To je razumljivo jer izračunava svaki indeks pomoću hash koda ključa. Štoviše, povremeno se izvrši ponovno usređivanje, preuređujući redoslijed strukture podataka.

Stoga, dodajmo neke unose i provjerimo izlaz:

Tablica tablice = nova tablica tablice (); table.put (nova Word ("1"), "jedan"); table.put (nova Word ("2"), "dva"); // ... table.put (nova Word ("8"), "osam"); Iterator it = table.entrySet (). iterator (); while (it.hasNext ()) {Map.Entry entry = it.next (); // ...}}
pet četiri tri dva jedan osam sedam

6. Hashtable nasuprot HashMap

Hashtable i HashMap pružaju vrlo sličnu funkcionalnost.

Oboje pružaju:

  • Neuspješna iteracija
  • Nepredvidivi redoslijed ponavljanja

Ali postoje i neke razlike:

  • HashMap ne pruža nijednu Nabrajanje, dok Hashtable pruža ne brzi pad Nabrajanje
  • Hashtable ne dopušta null tipke i null vrijednosti, dok HashMap dopusti jedan null ključ i bilo koji broj null vrijednosti
  • Hashtable'S metode su sinkronizirane dok HashMaps'S metode nisu

7. Hashtable API u Javi 8

Java 8 predstavila je nove metode koje pomažu da naš kod postane čišći. Konkretno, možemo se riješiti nekih ako blokovi. Pokažimo ovo.

7.1. getOrDefault ()

Recimo da moramo dobiti definiciju riječi „pasi dodijelite ga varijabli ako je na stolu. Inače, varijabli dodijelite "nije pronađeno".

Prije Jave 8:

Ključ riječi = nova riječ ("pas"); Definicija niza; if (table.containsKey (key)) {definition = table.get (key); } else {definicija = "nije pronađeno"; }

Nakon Jave 8:

definition = table.getOrDefault (ključ, "nije pronađen");

7.2. putIfAbsent ()

Recimo da trebamo staviti riječ „mačka samo ako još nije u rječniku.

Prije Jave 8:

if (! table.containsKey (nova Word ("mačka"))) {table.put (nova Word ("mačka"), definicija); }

Nakon Jave 8:

table.putIfAbsent (nova Word ("mačka"), definicija);

7.3. logičko uklanjanje ()

Recimo da moramo ukloniti riječ "mačka", ali samo ako je definicija "životinja".

Prije Jave 8:

if (table.get (nova Word ("mačka")). jednako ("životinja")) {table.remove (nova Word ("mačka")); }

Nakon Jave 8:

logički rezultat = table.remove (nova Word ("mačka"), "životinja");

Napokon, dok je stara ukloniti() metoda vraća vrijednost, vraća nova metoda boolean.

7.4. zamijeniti()

Recimo da trebamo zamijeniti definiciju "mačka", ali samo ako je stara definicija "mali udomaćeni mesožder sisavac".

Prije Jave 8:

if (table.containsKey (nova riječ ("mačka")) && table.get (nova riječ ("mačka")). jednako ("mali pripitomljeni mesožder sisavac")) {table.put (nova riječ ("mačka") ), definicija); }

Nakon Jave 8:

table.replace (nova Riječ ("mačka"), "mali udomaćeni mesožder sisavac", definicija);

7.5. computeIfAbsent ()

Ova metoda je slična putIfabsent (). Ali putIfabsent () uzima vrijednost izravno, i computeIfAbsent () uzima funkciju mapiranja. Vrijednost izračunava tek nakon što provjeri ključ, a to je učinkovitije, pogotovo ako je vrijednost teško dobiti.

table.computeIfAbsent (nova Word ("mačka"), ključ -> "životinja");

Stoga je gornji redak ekvivalentan:

if (! table.containsKey (cat)) {Definicija niza = "životinja"; // napominjemo da se proračuni odvijaju unutar bloka table.put (nova Word ("mačka"), definicija); }

7.6. computeIfPresent ()

Ova je metoda slična zamijeniti() metoda. Ali, opet, zamijeniti() uzima vrijednost izravno, i computeIfPresent () uzima funkciju mapiranja. Izračunava vrijednost unutar ako blok, zato je učinkovitiji.

Recimo da moramo promijeniti definiciju:

table.computeIfPresent (mačka, (ključ, vrijednost) -> key.getName () + "-" + vrijednost);

Stoga je gornji redak ekvivalentan:

if (table.containsKey (cat)) {String concatination = cat.getName () + "-" + table.get (cat); table.put (mačka, konkatinacija); }

7.7. izračunati ()

Sada ćemo riješiti još jedan zadatak. Recimo da imamo niz od Niz, gdje elementi nisu jedinstveni. Također, izračunajmo koliko pojavljivanja niza možemo dobiti u nizu. Evo niza:

Niz [] životinje = {"mačka", "pas", "pas", "mačka", "ptica", "miš", "miš"};

Također, želimo stvoriti Hashtable koja kao ključ sadrži životinju, a broj njezinih pojava kao vrijednost.

Evo rješenja:

Tablica tablice = nova tablica tablice (); za (String životinja: životinje) {table.compute (životinja, (ključ, vrijednost) -> (vrijednost == null? 1: vrijednost + 1)); }

Napokon, pobrinimo se da tablica sadrži dvije mačke, dva psa, jednu pticu i dva miša:

assertThat (table.values ​​(), hasItems (2, 2, 2, 1));

7.8. sjediniti()

Postoji još jedan način za rješavanje gornjeg zadatka:

za (String životinja: životinje) {table.merge (životinja, 1, (oldValue, vrijednost) -> (oldValue + vrijednost)); }

Drugi argument, 1, je vrijednost koja se preslikava na ključ ako ključ još nije na stolu. Ako je ključ već u tablici, tada ga izračunavamo kao oldValue + 1.

7.9. za svakoga()

Ovo je novi način pregledavanja unosa. Ispišimo sve unose:

table.forEach ((k, v) -> System.out.println (k.getName () + "-" + v)

7.10. zamjeni sve()

Uz to, sve vrijednosti možemo zamijeniti bez ponavljanja:

table.replaceAll ((k, v) -> k.getName () + "-" + v);

8. Zaključak

U ovom smo članku opisali svrhu strukture hash tablice i pokazali kako zakomplicirati strukturu tablice s izravnom adresom kako bi je dobili.

Pored toga, pokrili smo što su sudari i koliki je faktor opterećenja u a Hashtable. Također, naučili smo zašto poništiti jednako () i hashCode () za ključne objekte.

Napokon, razgovarali smo o tome HashtableSvojstva i specifični API za Java 8.

Kao i obično, cjeloviti izvorni kod dostupan je na Githubu.