Frage zu HashMaps

sserio

Bekanntes Mitglied
Ich habe mir HashMaps angeschaut und gemerkt, dass sie ziemlich nützlich sind. Jedoch habe ich hier ein Problem. In einem Youtube video wurde geagt, dass diese "geordnete" Listen sind. Wenn ich zum Beispiel 1,3,6,3 reinpacke, wird 1,3,3,6 in der Map stehen. Ich habe das mal ausprobiert um mich selbst davon zu überzeugen, wurde jedoch davon überrascht, dass es nicht geordnet ist. Einfach nur gelogen oder habe ich was falsch gemacht.
Java:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        var list = new ArrayList<>();
        for (var i = 0; i < 10; i++) {
            list.add(i);
            for (var j = 0; j < 10; j++) {
                list.add(j);
            }
        }
        HashMap<Integer, Integer> hashMap = new HashMap<>();
        for (int i = 0; i < list.size(); i++) {
            hashMap.put(i, (Integer) list.get(i));
        }
        Iterator iterator = hashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            System.out.println(((Map.Entry<?, ?>)iterator.next()).getValue());
        }
    }
}
 
Sicher kannst Du Dir z. B. die Schlüsselmenge geben lassen und diese sortieren. Das ändert aber nichts daran, dass ein HashSet eben keine Ordnung hat.

Der Hintergrund ist einfach der, dass aus dem Schlüssel unmittelbar ein Hashwert und daraus ein Index berechnet wird, der angibt, in welchem "Bucket" der Eintrag zu finden ist.
 
Sicher kannst Du Dir z. B. die Schlüsselmenge geben lassen und diese sortieren. Das ändert aber nichts daran, dass ein HashSet eben keine Ordnung hat.

Der Hintergrund ist einfach der, dass aus dem Schlüssel unmittelbar ein Hashwert und daraus ein Index berechnet wird, der angibt, in welchem "Bucket" der Eintrag zu finden ist.
Aber wofür ist es dann sinnvoll wenn ich auch einfach über eine Liste iterieren kann und so einen wert suchen kann . Oder halt contains() benutzen. Ich sehe jetzt nicht den Sinn dahinter. Bei Sets sehe ich einen, z.B dass es keine Möglichkeit gibt etwas zu duplizieren . Aber soo was bringts?
 
zusätzlich kannst du deine Logik sauberer aufbauen zb mit einer hashmap die als key ein enum hat dann kannst du per enum werte zugreifen = sehr übersichtlich 🙂
 
eine menge von objekten bei einer klasse aber diese klasse darf keine objekte erzeugen sondern diese darf nur diese von anfang festgelegte menge an objekten haben
 
Aber wofür ist es dann sinnvoll wenn ich auch einfach über eine Liste iterieren kann und so einen wert suchen kann . Oder halt contains() benutzen. Ich sehe jetzt nicht den Sinn dahinter. Bei Sets sehe ich einen, z.B dass es keine Möglichkeit gibt etwas zu duplizieren . Aber soo was bringts?
@LimDul hat es ja schon beantwortet: der Zugriff erfolgt in (im Wesentlichen) konstanter Zeit.

Jedes Object hat in Java eine Methode hashCode(), die einen int-Wert liefert. Der wird für die Schlüssel der HashMap verwendet. Im einfachsten Fall kannst Du Dir ein Array der Größe m vorstellen. Um den Index zu berechnen, kannst Du also key.hashCode() % m rechnen. Hinzu kommen dann noch Strategien zur Kollisionsbehandlung.

Mal ein einfaches Beispiel für ein perfektes (=kollisionsfreies) Hashing: die Hashwerte der Monatsnamen lassen sich auf 24 mögliche Indizes ohne Kollisionen verteilen:
Java:
public class MonatsHashMap {
    String[] keys = {
            "März", "Juli", null, null, null, null, 
            "August", "Oktober", "September", null, "Februar", "Juni", 
            "Januar", null, null, null, null, null, 
            "Mai", "Dezember", null, "November", null, "April" };
    Object[] values = new Object[keys.length];

    public Object get(String monat) {
        int index = getIndex(monat);
        return equalsKey(index, monat) ? values[index] : null;
    }

    public void set(String monat, Object value) {    
        int index = getIndex(monat);
        if (equalsKey(index, monat)) {
            values[index] = value;
        }
    }

    private int getIndex(String monat) { return Math.abs(monat.hashCode()) % 26 - 1; }

    private boolean equalsKey(int index, String monat) {
        return index >= 0 && index < keys.length && keys[index] != null && keys[index].equals(monat);
    }
}
Hier wird nicht lange in einer Datenstruktur gesucht; es wird einfach der Index berechnet (getIndex()), dann wir noch überprüft, ob der angegebene Key auch wirklich ein Key der "HashMap" ist und dann wird direkt über den berechneten Index auf die Werte zugegriffen.
 
Aber wofür ist es dann sinnvoll wenn ich auch einfach über eine Liste iterieren kann und so einen wert suchen kann . Oder halt contains() benutzen. Ich sehe jetzt nicht den Sinn dahinter. Bei Sets sehe ich einen, z.B dass es keine Möglichkeit gibt etwas zu duplizieren . Aber soo was bringts?
Die Hashmap ist nicht sortiert, dafür gibt's z.B. 2 Typen von SortedMaps: LinkedHashMap und TreeMap. Ersteres speichert die Werte in der
Reihenfolge, wie sie gesetzt wurden, das zweite in ihrer "natürlichen" Reihenfolge, z.B. aufsteigende Ints.
Selbiges gilt für Sets: HashSets sind nicht sortiert, LinkedHashSets und Treesets schon.


Enums sind was ganz Eigenes, hier ist die Anzahl der verschiedenen Werte immer gleich. Erstellt man eine EnumMap und ein EnumSet, so entspricht die Reihenfolge der Werte in diesen der Reihenfolge der Werte, wie sie im Enum-Quellcode stehen. EnumMaps sind damit die schnellsten aller Maps, letztendlich stellen sie nur eine Art Wrapper für ein Array dar, selbiges gilt für die Sets.
 
Aber wofür ist es dann sinnvoll wenn ich auch einfach über eine Liste iterieren kann und so einen wert suchen kann .
Es wurde schon vieles gesagt, aber dennoch auch mein Senf dazu: Der große Vorteil von Map<> ist, dass der Zugriff auf den gespeicherten Wert effizient über einen Schlüssel erfolgt. Die Anwendung dafür kann vielfältig sein. Nimm an du hast eine Klasse Order, die eine Bestellung aufnimmt und eine Klasse OrderId, die der Bestellnummer entspricht. Mit einer Map<OrderId, Order> orders erhältst du direkten Zugriff auf Bestellungen über die Bestellnummer, einfach mit Order order = orders.get(orderId);.

Oder ein Wörterbuch mit dem deutschen Wort als Schlüssel und dem fremdsprachigen Wort als Value.

Und schließlich ist es auch möglich ganze Strukturen in der Map<> zu verwalten, z. B. Map<Integer, List<String>> oder Map<CustomerId, Map<OrderId, Order>>. Im ersten Fall kannst du über einen Integer-Schlüssel auf eine Liste von Strings zugreifen und im Zweiten über eine Kundennummer auf eine Map von Bestellungen (etwas hinkendes Beispiel, aber es geht um das Prinzip).
 
Zuletzt bearbeitet:
Die von @Neumi5694 angesprochene EnumMap kann man sich etwa so vorstellen:
Java:
enum Monat {JANUAR, FEBRUAR, MAERZ, ARPIL, MAI, JUNI, JULI, AUGUST, SEPTEMBER, OKTOBER, NOVEMBER, DEZEMBER};

public class MonatsEnumMap {
    Object[] values = new Object[Monat.values().length];

    public Object get(Monat monat) {
        return values[monat.ordinal()];
    }

    public void set(Monat monat, Object value) {    
        values[monat.ordinal()] = value;
    }
}
 
Die von @Neumi5694 angesprochene EnumMap kann man sich etwa so vorstellen:
Java:
enum Monat {JANUAR, FEBRUAR, MAERZ, ARPIL, MAI, JUNI, JULI, AUGUST, SEPTEMBER, OKTOBER, NOVEMBER, DEZEMBER};

public class MonatsEnumMap {
    Object[] values = new Object[Monat.values().length];

    public Object get(Monat monat) {
        return values[monat.ordinal()];
    }

    public void set(Monat monat, Object value) {   
        values[monat.ordinal()] = value;
    }
}
Also muss man nicht HashMap<> hm = new Hashmap schreiben, sondern kann einfach ein Object array benutzen oder wie. Also ich verstehe das Codebeispiel nicht ganz. Es wird hier eine eingene HashMap programmiert oder wie
 
Die von @Neumi5694 angesprochene EnumMap kann man sich etwa so vorstellen:
Java:
enum Monat {JANUAR, FEBRUAR, MAERZ, ARPIL, MAI, JUNI, JULI, AUGUST, SEPTEMBER, OKTOBER, NOVEMBER, DEZEMBER};

public class MonatsEnumMap {
    Object[] values = new Object[Monat.values().length];

    public Object get(Monat monat) {
        return values[monat.ordinal()];
    }

    public void set(Monat monat, Object value) {   
        values[monat.ordinal()] = value;
    }
}
und was ist das für ein ordinal() ? ist das dann halt einfach nur getIndex() in anders geschrieben
 
Also muss man nicht HashMap<> hm = new Hashmap schreiben, sondern kann einfach ein Object array benutzen oder wie. Also ich verstehe das Codebeispiel nicht ganz. Es wird hier eine eingene HashMap programmiert oder wie
Ja, die Beispiele sollen nur ganz grob veranschaulichen, wie eine HashMap bzw. eine EnumMap funktioniert (funktionieren kann). Wichtig dabei: es muss keine Datenstruktur durchsucht werden, da eine Suche relativ zeitaufwändig ist. Die Position wird einfach aus dem Schlüssel berechnet (bei der EnumMap entspricht die Postiion im Array z. B. einfach ordinal()).
 
Hier mal eine MonatsMap ohne Hashing:
Java:
public class MonatsMap {
    private String[] keys = {
            "Januar", "Februar", "März", "April", "Mai", "Juni", 
            "Juli", "August", "September", "Oktober", "November", "Dezember" };
    private Object[] values = new Object[keys.length];

    public Object get(String key) {
        int index = indexOf(key);
        return (index >= 0) ? values[index] : null;
    }

    public void set(String key, Object value) {
        int index = indexOf(key);
        if (index >= 0) {
            values[index] = value;
        }
    }

    private int indexOf(String key) {
        int index = keys.length - 1;
        while (index >= 0 && !keys[index].equals(key)) {
            index--;
        }
        return index;
    }
}

Die Funktionalität ist die gleiche: die Map bildet Monatsnamen auf Werte ab.

Wo liegt der Unterschied zur MonatsHashMap? Ganz einfach: die indexOf-Methode (MonatsMap) durchsucht alle Monatsnamen; die Laufzeit ist also abhängig von der Position des Monatsnamens im Array. Wird ein Schlüssel angegeben, der überhaupt nicht im Array vorkommt, müssen alle 12 Monatsnamen überprüft werden -> dauert am längsten. Die getIndex-Methode (MonatsHashMap) hingegen vergleicht gar nichts sondern führt eine Rechnung aus. Der Zeitaufwand hierfür ist konstant - egal, ob und wenn ja, an welcher Stelle der Schlüssel im Array steckt
 
und was ist das für ein ordinal() ? ist das dann halt einfach nur getIndex() in anders geschrieben
Nein, ganz und gar nicht. Das hat mit dem Indes einer Liste nicht das Geringste zu tun.
Jedes Enum hat ein Ordinal, das sich nicht verändert, immer gleich ist, egal an welcher Position in einer Liste sich das Objekt befindet.

Java:
enum MyColor {Red,Green,Yellow};
List<MyColor> myList = Arrays.asList(MyColor.Green,MyColor.Red);

//Und hier die passenden true Abfragen

MyColor.Red.ordinal() == 0
myList.indexOf(MyColor.Red) == 1

MyColor.Green.ordinal() == 1
myList.indexOf(MyColor.Green) == 0

MyColor.Yellow.ordinal() == 2
myList.indexOf(MyColor.Yellow) == -1

//Jetzt zum Set
EnumSet<MyColor> set = EnumSet.of(MyColor.Yellow, MyColor.Green);
set.iterator().next() == MyColor.Green; //Green steht in der Klasse vor Yellow, steht also auch im EnumSet weiter vorne, in einem HashSet wäre es "zufällig".
new ArrayList(set).indexOf(MyColor.Green) == 0 //immer noch unterschiedlich von ordinal
//wenn wir jetzt aber noch Red hinzufügen ...
set.add(MyColor.Red);
set.iterator().next() == MyColor.Red;//Tadaaa! Red hat sich vor Green geschoben
new ArrayList(set).indexOf(MyColor.Green) == 1

Was in Mihe's erstem Beispiel fehlt (deshalb vielleicht etwas verwirrend), ist, ob der Key überhaupt in der Map vorhanden ist und wie viele Keys in der Map vorhanden sind. Da es sich nur um ein Anschauungsbeispiel handelt, ist das nicht weiter schlimm (tatsächlich sind EnumSets und EnumMaps um einiges komplexer, hier geht es nur um die Grundfunktion).
Wie es intern genau funktioniert, braucht dich nicht weiter zu interessieren (aber falls doch, kannst du ja mal in den Quellcode einer EnumMap reinschauen), bei Maps ist wichtig, dass sie nach außen alle gleich funktionieren.
 
Zuletzt bearbeitet:
um noch meinen obulus hinein zuwerfen

ich denke das ist alles weiter als der TE überhaupt wollte

zb du machst ein programm und das schickt objekte an einen server und der server soll dann was machen dann kannst du zb so ein objekt bauen
Java:
class NetzwerkObjekt{
    HashMap<TYP, String>
      
    ....
  
}
enum TYP {
    EINFUEGEN, LÖSCHEN;
}
auf der server seite kannst du dann schauen in der hashmap
Java:
if( nachricht.get(TYP.Einfuegen).equals("bild1"))
{
    System.out.println("Bild 1 will eingefügt werden");
}
somit kannst du dir halt eine "lesbare" nachricht zusammen bauen... das kannst du natürlich dann verwenden wie du lustig bist

ob du da eine Liste oder ein normales array hernimmst ist ansich komplett egal aber das ist halt dann lesbarer wenn du mit hashmaps umgehst

die sortierung ist hier auch komplett egal... dich interessierts nich ob einfuegen oder löschen am anfang steht oder nicht

die zugriffszeit ist immer gleich und hat hier keinerlei auswirkung... du kannst sie sortieren aber...warum?
 

Zurück
Oben