Classe Java que implementa o Map e mantém a ordem de inserção?

463

Estou procurando uma classe em java que tenha associação de valor-chave, mas sem usar hashes. Aqui está o que estou fazendo atualmente:

  1. Adicione valores a Hashtable.
  2. Obtenha um iterador para o Hashtable.entrySet().
  3. Repita todos os valores e:
    1. Obtenha um Map.Entrypara o iterador.
    2. Crie um objeto do tipo Module(uma classe personalizada) com base no valor.
    3. Adicione a classe a um JPanel.
  4. Mostre o painel.

O problema com isso é que eu não tenho controle sobre a ordem em que recebo os valores de volta, portanto não consigo exibir os valores em uma determinada ordem (sem codificar a ordem).

Eu usaria um ArrayListou Vectorpara isso, mas mais tarde no código, preciso pegar o Moduleobjeto para uma determinada chave, o que não posso fazer com um ArrayListou Vector.

Alguém sabe de uma classe Java de código aberto / livre que fará isso ou de uma maneira de obter valores com Hashtablebase em quando foram adicionados?

Obrigado!

Shane
fonte
1
Você não precisa usar entryset / map.entry. você pode iterar sobre chaves e valores usando hashtable.keys como uma enumeração ou usando hashtable.keyset.iterator.
John Gardner
5
Tomei a liberdade de alterar o título, pois não usar hashes não é realmente o problema, mas manter a ordem de inserção.
Joachim Sauer
Pergunta semelhante, Java ordenou o mapa
Basil Bourque

Respostas:

734

Eu sugiro um LinkedHashMapou um TreeMap. A LinkedHashMapmantém as chaves na ordem em que foram inseridas, enquanto a TreeMapé mantida classificada por meio de uma ordem Comparatornatural Comparabledos elementos.

Como ele não precisa manter os elementos classificados, LinkedHashMapdeve ser mais rápido na maioria dos casos; TreeMaptem O(log n)desempenho de containsKey, get, put, e remove, de acordo com o JavaDocs, enquanto LinkedHashMapé O(1)para cada um deles.

Se sua API que espera apenas uma ordem de classificação previsível, em oposição a uma ordem de classificação específica, considere usar as interfaces que essas duas classes implementam, NavigableMapou SortedMap. Isso permitirá que você não vaze implementações específicas em sua API e alterne para uma dessas classes específicas ou uma implementação completamente diferente à vontade posteriormente.

Michael Myers
fonte
2
Isso não funcionará para mim porque, conforme javadocs, isso fornece apenas valores ordenados (por meio da chamada values ​​()). Existe uma maneira de obter instâncias Map.Entry ordenadas?
Cory Kendall
1
@CoryKendall: O TreeMap não funciona? Ele deve ser classificado por chaves, não por valores.
Michael Myers
1
Meu erro, pensei que Sets não estavam classificados.
Cory Kendall
61
Observe: A classificação de um TreeMap é baseada na ordem natural das chaves: "O mapa é classificado de acordo com a ordem natural de suas chaves". O LinkedHashMap é classificado na ordem de inserção. Grande diferença!
Jop van Raaij #
3
@AlexR: Isso só é verdade se o LinkedHashMap foi criado usando o construtor especial fornecido para esse fim. Por padrão, a iteração está na ordem de inserção.
Michael Myers
22

O LinkedHashMap retornará os elementos na ordem em que foram inseridos no mapa quando você iterar sobre keySet (), entrySet () ou valores () do mapa.

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());
}

Isso imprimirá os elementos na ordem em que foram colocados no mapa:

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

Se um mapa imutável atende às suas necessidades , existe uma biblioteca do Google chamada goiaba (consulte também perguntas sobre goiaba )

O Guava fornece ao ImmutableMap uma ordem de iteração confiável especificada pelo usuário. Este ImmutableMap tem desempenho O (1) para containsKey, get. Obviamente, colocar e remover não são suportados.

Os objetos ImmutableMap são construídos usando os métodos estáticos elegantes de conveniência de () e copyOf () ou um objeto Builder .

jvdneste
fonte
6

Você pode manter um Map(para pesquisa rápida) e List(por pedido), mas um LinkedHashMappode ser o mais simples. Você também pode tentar um SortedMapexemplo TreeMap, que pode ter qualquer ordem que você especificar.

Peter Lawrey
fonte
1

Não sei se é de código aberto, mas depois de pesquisar um pouco, encontrei essa implementação do Map usando ArrayList . Parece ser Java anterior à 1.5, portanto, você pode querer generalizá-lo, o que deve ser fácil. Observe que esta implementação tem acesso O (N), mas isso não deve ser um problema se você não adicionar centenas de widgets ao seu JPanel, o que não deveria ser feito.

jpalecek
fonte
1

Sempre que preciso manter a ordem natural das coisas conhecidas com antecedência, uso um EnumMap

as chaves serão enumeradas e você poderá inserir na ordem que desejar, mas quando iterar, iterará na ordem enum (a ordem natural).

Além disso, ao usar o EnumMap, não deve haver colisões que possam ser mais eficientes.

Eu realmente acho que o uso de enumMap cria um código legível limpo. Aqui está um exemplo

j2emanue
fonte
1

Você pode usar o LinkedHashMap para a ordem de inserção principal no mapa

Os pontos importantes sobre a classe Java LinkedHashMap são:

  1. Ele contém apenas elementos únicos.
  2. Um LinkedHashMap contém valores com base na chave 3. Pode ter uma chave nula e vários valores nulos. 4.É o mesmo que o HashMap, mas mantém a ordem de inserção

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

Mas se você deseja classificar valores no mapa usando o objeto definido pelo usuário ou qualquer chave de tipo de dados primitiva, deve usar o TreeMap Para obter mais informações, consulte este link

Pankaj Goyal
fonte
0

Você pode usar LinkedHashMap<K, V>ou implementar seu próprio CustomMap que mantém a ordem de inserção.

Você pode usar o seguinte CustomHashMapcom os seguintes recursos:

  • A ordem de inserção é mantida, usando o LinkedHashMap internamente.
  • Teclas com null ou cadeias vazias não são permitidas.
  • Depois que a chave com valor é criada, não estamos substituindo seu valor.

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();
    }
}

Uso de 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             |

Se você souber que as CHAVES estão corrigidas, poderá usar o EnumMap. Obter os valores do formulário Propriedades / arquivos XML

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
fonte