Was sind Hashtable und Hashmap?

Hashtable und Hashmap stellen assoziative Suchtabellen dar, deren Einträge aus Schlüssel-Wert-Paaren bestehen.

Die Hashtable wurde ab Java 1.2 durch die Hashmap ergänzt, um dem Collections-Framework zu entsprechen. Beides sind Implementierungen einer Hashtabelle, die sich sehr ähnlich sind. Oracle empfiehlt die Verwendung von Hashmap, die jedoch im Gegensatz zur Hashtable nicht synchronisiert ist.
Beide speichern Schlüssel-Wert-Paare, deren Schlüssel immer eindeutig sind. Beim Setzen eines neuen Wertes mit einem vorhandenen Schlüssel wird dessen Wert überschrieben. Schlüssel und Wert können prinzipiell Objekte eines beliebigen Typs sein, die allerdings die Methoden equals() und hashCode() implementieren müssen. Hashmap erlaubt, Hashtable verbietet dagegen null-Einträge. Primitive Datentypen sind nicht erlaubt und werden beim Setzen als Schlüssel oder Wert durch Boxing in Objekte der entsprecheden Wrapper-Klassen gewandelt.

Konstruktoren von Hashtable und Hashmap besitzen zwei Parameter, die für die Effizienz und Geschwindigkeit ihres Arbeitens entscheidend sind: die initial capacity (anfängliche Kapazität) und ihr load factor (Füllfaktor). Werden beide Werte im Konstruktor nicht gesetzt, beträgt die Anfangskapazität bei der Hashtable 11, bei der Hashmap 16 Wertepaare, bei einem Füllfaktor von jeweils 75% (0.75). Der Füllfaktor gibt an, in welchem Maße die Sammlung maximal gefüllt sein darf. Wird dieser Wert beim Einfügen neuer Elemente überschritten, wird sie intern neu angelegt und berechnet. Der benötigte Overhead der Sammlung hat mit der internen Umsetzung der Einträge in ein Array und der Behandlung von Kollisionen doppelter Hashwerte1 zu tun und ist entscheidend bei geschwindigkeitskritischen Anwendungen.
Für eine wenig zeitkritische Verwendung ist es empfehlenswert, den Füllfaktor nicht zu ändern. So ist der effizienteste Kompromiss zwischen Geschwindigkeit und Speicherbedarf gewährleistet.

public class HashMapBeispiel {

    public static void main(String[] args) {
        HashMap<Integer, String> ht = new HashMap<Integer, String>();
        String s;

        ht.put(2, "Gilera");
        ht.put(1, "Moto Guzzi");
        ht.put(3, "Morbidelli");
        System.out.println("Durchlauf 1:");
        for (Integer elem : ht.keySet()) {
            s = ht.get(elem);
            System.out.println(elem + " - " + s);
        }
        // Wert unter Key "1" wird ersetzt
        ht.put(1, "Ducati");
        System.out.println("\nDurchlauf 2:");
        Set<Integer> keys = ht.keySet();
        Iterator<Integer> iterator = keys.iterator();
        while (iterator.hasNext()) {
            Integer i = (Integer) iterator.next();
            s = ht.get(i);
            System.out.println(i + " - " + s);
        }

        System.out.println("\nMoto Guzzi vorhanden? " + ht.containsValue("Moto Guzzi"));

        ht.put(4, "Moto Morini");
        System.out.println("\nAnzahl Elemente nach einf\u00FCgen von '4': "
                + ht.size());
        System.out.println("\nDurchlauf 3:");
        Collection<String> vals = ht.values();
        Iterator<String> iter = vals.iterator();
        while (iter.hasNext()) {
            System.out.println(iter.next());
        }

        ht.remove(4);
        System.out.println("\nAnzahl Elemente nach entfernen von '4': "
                + ht.size());

        ht.clear();
        System.out.println("\nAnzahl Elemente nach clear(): " + ht.size());
    }
}

Im Beispiel sind einige gängige Anwendungsweisen einer HashMap demonstriert.
Die HashMap ist ein generischer Typ, bei dem festgelegt werden muss, welche Typen für die Angabe von Schlüssel und Wert verwendet werden. Im Beispiel sind dies Integer für den Schlüssel und String für den Wert. Die Angabe von primitiven int-Werten beim Einfügen mit put() ist möglich, da diese, wie oben erläutert, in Integer gewandelt werden.
Beim Abfragen aller Einträge kann die Methode keySet() verwendet werden. Sie liefert ein Set aller Schlüssel, das in einer Schleife durchlaufen werden kann. Durch Übergabe der einzelnen Schlüssel als Parameter an die Methode get() kann dann auf seinen zugehörigen Wert zugegriffen werden.

Nach dem Ersetzen des Wertes unter Schlüssel '1' wird neben der oben verwendeten erweiterten for-Schleife eine zweite Möglichkeit demonstriert, um das Schlüsselset zu durchlaufen. Ein Iterator-Objekt wird durch die Methode iterator() erzeugt. Dessen Methode hasNext() prüft, ob weitere Elemente vorhanden sind und kann so als Abbruchbedingung in einer Schleife Verwendung finden. Dort wird das nächste vorhandenen Element - hier der nächste Schlüssel - mit next() ermittelt.
Auch in der dritten und letzen Schleife wird ein Iterator verwendet, der nun jedoch die mit values() ermittelten Werte durchläuft.

Duch Übergabe des Schlüssels an die Methode remove() kann ein Eintrag aus der HashMap entfernt werden; durch clear() kann diese vollständig geleert werden.

1) Doppelte Hashwerte können intern bei der Berechnung aus den Schlüsseln der Einträge entstehen. Sie werden bei Auftreten durch spezielle Kollisionsmechanismen behandelt.