Java-Klasse, die Map implementiert und die Einfügereihenfolge beibehält?

463

Ich suche nach einer Klasse in Java, die eine Schlüsselwertzuordnung hat, aber keine Hashes verwendet. Folgendes mache ich gerade:

  1. Hinzufügen von Werten zu a Hashtable.
  2. Holen Sie sich einen Iterator für die Hashtable.entrySet().
  3. Durchlaufen Sie alle Werte und:
    1. Holen Sie sich ein Map.Entryfür den Iterator.
    2. Erstellen Sie ein Objekt vom Typ Module(eine benutzerdefinierte Klasse) basierend auf dem Wert.
    3. Fügen Sie die Klasse einem JPanel hinzu.
  4. Zeigen Sie das Panel.

Das Problem dabei ist, dass ich keine Kontrolle über die Reihenfolge habe, in der ich die Werte zurückerhalte, sodass ich die Werte in einer bestimmten Reihenfolge nicht anzeigen kann (ohne die Reihenfolge fest zu codieren).

Ich würde ein ArrayListoder Vectordafür verwenden, aber später im Code muss ich das ModuleObjekt für einen bestimmten Schlüssel abrufen, was ich mit einem ArrayListoder nicht tun kann Vector.

Kennt jemand eine Free / Open-Source-Java-Klasse, die dies tut, oder eine Möglichkeit, Werte aus einer Klasse zu ermitteln, die darauf Hashtablebasiert, wann sie hinzugefügt wurden?

Vielen Dank!

Shane
quelle
1
Sie müssen entryset / map.entry nicht verwenden. Sie können Schlüssel und Werte durchlaufen, indem Sie hashtable.keys als Aufzählung oder hashtable.keyset.iterator verwenden.
John Gardner
5
Ich habe mir erlaubt, den Titel zu ändern, da es eigentlich nicht das Problem ist, keine Hashes zu verwenden, sondern die Einfügereihenfolge beizubehalten.
Joachim Sauer
Ähnliche Frage, Java Ordered Map
Basil Bourque

Antworten:

734

Ich schlage ein LinkedHashMapoder ein TreeMap. A LinkedHashMaphält die Schlüssel in der Reihenfolge, in der sie eingefügt wurden, während a TreeMapüber a Comparatoroder die natürliche ComparableReihenfolge der Elemente sortiert bleibt .

Da die Elemente nicht sortiert bleiben müssen, LinkedHashMapsollte es in den meisten Fällen schneller sein. TreeMaphat O(log n)Leistung für containsKey, get, put, und remove, nach dem Javadocs, während LinkedHashMapist O(1)für jeden.

Wenn Ihre API im Gegensatz zu einer bestimmten Sortierreihenfolge nur eine vorhersehbare Sortierreihenfolge erwartet, sollten Sie die von diesen beiden Klassen implementierten Schnittstellen verwenden, NavigableMapoder SortedMap. Auf diese Weise können Sie keine bestimmten Implementierungen in Ihre API verlieren und anschließend nach Belieben zu einer dieser spezifischen Klassen oder zu einer völlig anderen Implementierung wechseln.

Michael Myers
quelle
2
Dies funktioniert bei mir nicht, da dies laut Javadocs nur geordnete Werte ergibt (über den Aufruf values ​​()). Gibt es eine Möglichkeit, geordnete Map.Entry-Instanzen zu erhalten?
Cory Kendall
1
@CoryKendall: Funktioniert TreeMap nicht? Es soll nach Schlüsseln sortiert werden, nicht nach Werten.
Michael Myers
1
Mein Fehler, ich dachte, Sets wären unsortiert.
Cory Kendall
61
Bitte beachten Sie: Die Sortierung einer TreeMap basiert auf der natürlichen Reihenfolge der Schlüssel: "Die Karte wird nach der natürlichen Reihenfolge ihrer Schlüssel sortiert". Die LinkedHashMap ist nach Einfügereihenfolge sortiert. Großer Unterschied!
Jop van Raaij
3
@AlexR: Dies gilt nur, wenn die LinkedHashMap mit dem speziellen Konstruktor erstellt wurde, der für diesen Zweck bereitgestellt wird. Standardmäßig erfolgt die Iteration in der Einfügereihenfolge.
Michael Myers
22

LinkedHashMap gibt die Elemente in der Reihenfolge zurück, in der sie in die Map eingefügt wurden, wenn Sie über keySet (), entrySet () oder values ​​() der Map iterieren.

Map<String, String> map = new LinkedHashMap<String, String>();

map.put("id", "1");
map.put("name", "rohan");
map.put("age", "26");

for (Map.Entry<String, String> entry : map.entrySet()) {
    System.out.println(entry.getKey() + " = " + entry.getValue());
}

Dadurch werden die Elemente in der Reihenfolge gedruckt, in der sie in die Karte eingefügt wurden:

id = 1
name = rohan 
age = 26 
Praveen Kishor
quelle
16

Wenn eine unveränderliche Karte Ihren Anforderungen entspricht, gibt es eine Bibliothek von Google namens Guave (siehe auch Guavenfragen ).

Guava bietet eine ImmutableMap mit zuverlässiger benutzerdefinierter Iterationsreihenfolge. Diese ImmutableMap hat O (1) -Leistung für enthältKey, get. Offensichtlich werden Put und Remove nicht unterstützt.

ImmutableMap- Objekte werden mithilfe der eleganten statischen Komfortmethoden von () und copyOf () oder eines Builder- Objekts erstellt.

jvdneste
quelle
6

Sie können a Map(zur schnellen Suche) und List(zur Bestellung) pflegen , aber a ist LinkedHashMapmöglicherweise die einfachste. Sie können auch ein SortedMapBeispiel ausprobieren TreeMap, das eine beliebige Reihenfolge hat, die Sie angeben.

Peter Lawrey
quelle
1

Ich weiß nicht, ob es Open Source ist, aber nach ein wenig googeln habe ich diese Implementierung von Map mithilfe von ArrayList gefunden . Es scheint Java vor 1.5 zu sein, daher möchten Sie es möglicherweise generisieren, was einfach sein sollte. Beachten Sie, dass diese Implementierung über O (N) -Zugriff verfügt. Dies sollte jedoch kein Problem sein, wenn Sie Ihrem JPanel nicht Hunderte von Widgets hinzufügen, was Sie sowieso nicht tun sollten.

jpalecek
quelle
1

Sie können meine Linked Tree Map- Implementierung ausprobieren .

Lawrence Dol
quelle
1

Wann immer ich die natürliche Reihenfolge der Dinge beibehalten muss, die im Voraus bekannt sind, verwende ich eine EnumMap

Die Schlüssel sind Aufzählungen und Sie können sie in beliebiger Reihenfolge einfügen. Wenn Sie sie jedoch wiederholen, wird sie in der Aufzählungsreihenfolge (der natürlichen Reihenfolge) wiederholt.

Auch bei Verwendung von EnumMap sollten keine Kollisionen auftreten, die effizienter sein können.

Ich finde wirklich, dass die Verwendung von enumMap für sauber lesbaren Code sorgt. Hier ist ein Beispiel

j2emanue
quelle
1

Sie können LinkedHashMap verwenden, um die Einfügereihenfolge in Map zu bestimmen

Die wichtigen Punkte der Java LinkedHashMap-Klasse sind:

  1. Es enthält nur einzigartige Elemente.
  2. Eine LinkedHashMap enthält Werte, die auf dem Schlüssel 3 basieren. Sie kann einen Nullschlüssel und mehrere Nullwerte enthalten. 4. Es ist dasselbe wie HashMap, stattdessen wird die Einfügereihenfolge beibehalten

    public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> 

Wenn Sie jedoch Werte in der Karte mithilfe eines benutzerdefinierten Objekts oder eines primitiven Datentypschlüssels sortieren möchten, sollten Sie TreeMap verwenden . Weitere Informationen finden Sie unter diesem Link

Pankaj Goyal
quelle
0

Entweder können LinkedHashMap<K, V>Sie eine eigene CustomMap verwenden oder implementieren, die die Einfügereihenfolge beibehält.

Sie können Folgendes CustomHashMapmit den folgenden Funktionen verwenden:

  • Die Einfügereihenfolge wird beibehalten, indem LinkedHashMap intern verwendet wird.
  • Schlüssel mit nulloder leere Zeichenfolgen sind nicht zulässig.
  • Sobald ein Schlüssel mit Wert erstellt wurde, überschreiben wir seinen Wert nicht mehr.

HashMapvs LinkedHashMapvsCustomHashMap

interface CustomMap<K, V> extends Map<K, V> {
    public boolean insertionRule(K key, V value);
}

@SuppressWarnings({ "rawtypes", "unchecked" })
public class CustomHashMap<K, V> implements CustomMap<K, V> {
    private Map<K, V> entryMap;
    // SET: Adds the specified element to this set if it is not already present.
    private Set<K> entrySet;

    public CustomHashMap() {
        super();
        entryMap = new LinkedHashMap<K, V>();
        entrySet = new HashSet();
    }

    @Override
    public boolean insertionRule(K key, V value) {
        // KEY as null and EMPTY String is not allowed.
        if (key == null || (key instanceof String && ((String) key).trim().equals("") ) ) {
            return false;
        }

        // If key already available then, we are not overriding its value.
        if (entrySet.contains(key)) { // Then override its value, but we are not allowing
            return false;
        } else { // Add the entry
            entrySet.add(key);
            entryMap.put(key, value);
            return true;
        }
    }
    public V put(K key, V value) {
        V oldValue = entryMap.get(key);
        insertionRule(key, value);
        return oldValue;
    }
    public void putAll(Map<? extends K, ? extends V> t) {
        for (Iterator i = t.keySet().iterator(); i.hasNext();) {
            K key = (K) i.next();
            insertionRule(key, t.get(key));
        }
    }

    public void clear() {
        entryMap.clear();
        entrySet.clear();
    }
    public boolean containsKey(Object key) {
        return entryMap.containsKey(key);
    }
    public boolean containsValue(Object value) {
        return entryMap.containsValue(value);
    }
    public Set entrySet() {
        return entryMap.entrySet();
    }
    public boolean equals(Object o) {
        return entryMap.equals(o);
    }
    public V get(Object key) {
        return entryMap.get(key);
    }
    public int hashCode() {
        return entryMap.hashCode();
    }
    public boolean isEmpty() {
        return entryMap.isEmpty();
    }
    public Set keySet() {
        return entrySet;
    }
    public V remove(Object key) {
        entrySet.remove(key);
        return entryMap.remove(key);
    }
    public int size() {
        return entryMap.size();
    }
    public Collection values() {
        return entryMap.values();
    }
}

Verwendung von CustomHashMap:

public static void main(String[] args) {
    System.out.println("== LinkedHashMap ==");
    Map<Object, String> map2 = new LinkedHashMap<Object, String>();
    addData(map2);

    System.out.println("== CustomHashMap ==");
    Map<Object, String> map = new CustomHashMap<Object, String>();
    addData(map);
}
public static void addData(Map<Object, String> map) {
    map.put(null, "1");
    map.put("name", "Yash");
    map.put("1", "1 - Str");
    map.put("1", "2 - Str"); // Overriding value
    map.put("", "1"); // Empty String
    map.put(" ", "1"); // Empty String
    map.put(1, "Int");
    map.put(null, "2"); // Null

    for (Map.Entry<Object, String> entry : map.entrySet()) {
        System.out.println(entry.getKey() + " = " + entry.getValue());
    }
}

O / P:

== LinkedHashMap == | == CustomHashMap ==
null = 2            | name = Yash
name = Yash         | 1 = 1 - Str
1 = 2 - Str         | 1 = Int
 = 1                |
  = 1               |
1 = Int             |

Wenn Sie wissen, dass die KEYs behoben sind, können Sie EnumMap verwenden. Rufen Sie die Werte aus Eigenschaften / XML-Dateien ab

EX:

enum ORACLE {
    IP, URL, USER_NAME, PASSWORD, DB_Name;
}

EnumMap<ORACLE, String> props = new EnumMap<ORACLE, String>(ORACLE.class);
props.put(ORACLE.IP, "127.0.0.1");
props.put(ORACLE.URL, "...");
props.put(ORACLE.USER_NAME, "Scott");
props.put(ORACLE.PASSWORD, "Tiget");
props.put(ORACLE.DB_Name, "MyDB");
Yash
quelle