2009-12-20 24 views
89

He usado LinkedHashMap porque es importante el orden en que las teclas se ingresan en el mapa.Java LinkedHashMap get first or last entry

Pero ahora quiero obtener el valor de la clave en primer lugar (la primera entrada ingresada) o la última.

¿Debería haber un método como first() y last() o algo así?

¿Necesito tener un iterador para obtener la primera entrada de clave? ¡Es por eso que usé LinkedHashMap!

Gracias!

+2

La situación es de hecho desafortunado. Aquí está la solicitud de función (de baja prioridad) que proporcionaría lo que necesita: http://bugs.sun.com/view_bug.do?bug_id=6266354 –

Respuesta

111

La semántica de LinkedHashMap son las de un mapa, en lugar de LinkedList. Conserva el orden de inserción, sí, pero eso es un detalle de implementación, en lugar de un aspecto de su interfaz.

La forma más rápida de obtener la "primera" entrada es todavía entrySet().iterator().next(). Obtener la "última" entrada es posible, pero implicará repetir todo el conjunto de entradas llamando al .next() hasta llegar a la última. while (iterator.hasNext()) { lastElement = iterator.next() }

edición: Sin embargo, si usted está dispuesto a ir más allá de la API JavaSE, Apache Commons Collections tiene su propia aplicación LinkedMap, que cuenta con métodos como firstKey y lastKey, que hacen lo que usted está buscando. La interfaz es considerablemente más rica.

+0

Tengo que insertar la entrada (clave, valor) como primera entrada en mi mapa vinculado; O algo así como inserción basada en índice. ¿Es eso posible con las colecciones de Apache? –

+0

Los enlaces "LinkedMap", "firstKey" y "lastKey" están obsoletos, fyi. – Josh

+0

@HateStackLoveStack Reparado, gracias – skaffman

6

Una forma más de obtener la primera y la última entrada de un LinkedHashMap es utilizar el método "toArray" de Set interface.

Pero creo que iterar sobre las entradas en el conjunto de entrada y obtener la primera y la última entrada es un mejor enfoque.

El uso de métodos de matriz conduce a la advertencia de la forma "... necesidades de conversión sin marcar para ajustarse a ..." que no puede ser fijada [pero se puede solamente ser suprimido mediante el uso de los @SuppressWarnings de anotación (" desenfrenado")].

Aquí es un pequeño ejemplo para demostrar el uso del método de "toArray":

public static void main(final String[] args) { 
    final Map<Integer,String> orderMap = new LinkedHashMap<Integer,String>(); 
    orderMap.put(6, "Six"); 
    orderMap.put(7, "Seven"); 
    orderMap.put(3, "Three"); 
    orderMap.put(100, "Hundered"); 
    orderMap.put(10, "Ten"); 

    final Set<Entry<Integer, String>> mapValues = orderMap.entrySet(); 
    final int maplength = mapValues.size(); 
    final Entry<Integer,String>[] test = new Entry[maplength]; 
    mapValues.toArray(test); 

    System.out.print("First Key:"+test[0].getKey()); 
    System.out.println(" First Value:"+test[0].getValue()); 

    System.out.print("Last Key:"+test[maplength-1].getKey()); 
    System.out.println(" Last Value:"+test[maplength-1].getValue()); 
} 

// the output geneated is : 
First Key:6 First Value:Six 
Last Key:10 Last Value:Ten 

+4

el método toArray en sí mismo iterará sobre el hashmap de nuevo :) Por lo tanto, es bastante más ineficiente debido a los pocos ciclos adicionales desperdiciados en la creación de la matriz y el espacio asignado para la matriz. – Durin

1

I recomendaría el uso de ConcurrentSkipListMap que tiene firstKey() y lastKey() métodos

+1

ConcurrentSkipListMap requiere un comparador (o comparador natural), por lo que debe realizar un trabajo adicional para guardar el orden en que se colocan las entradas. –

+0

HashMap y específicamente LinkedHashMap proporciona acceso en promedio de O (1), a diferencia de SkipList, que proporciona acceso en promedio de O (logn). –

+0

"El mapa está ordenado de acuerdo con el orden natural de sus claves" –

0

Aunque LinkedHashMap no proporciona ninguna método para obtener el primer, último o cualquier objeto específico.

pero es bastante trivial para obtener:

  • Mapa orderMap = new LinkedHashMap();
    Establecer al = orderMap.keySet();

ahora usando el iterador en el objeto; puedes obtener cualquier objeto

1

Tal vez algo como esto:

LinkedHashMap<Integer, String> myMap; 

public String getFirstKey() { 
    String out = null; 
    for (int key : myMap.keySet()) { 
    out = myMap.get(key); 
    break; 
    } 
    return out; 
} 

public String getLastKey() { 
    String out = null; 
    for (int key : myMap.keySet()) { 
    out = myMap.get(key); 
    } 
    return out; 
} 
+2

lamentablemente getLastKey() aquí es O (n) ... – rogerdpack

0

Sí me encontré con el mismo problema, pero afortunadamente sólo necesita el primer elemento ... - Esto es lo que hice por él.

private String getDefaultPlayerType() 
{ 
    String defaultPlayerType = ""; 
    for(LinkedHashMap.Entry<String,Integer> entry : getLeagueByName(currentLeague).getStatisticsOrder().entrySet()) 
    { 
     defaultPlayerType = entry.getKey(); 
     break; 
    } 
    return defaultPlayerType; 
} 

Si necesita el último elemento, así - me vería en la forma de invertir el orden de su mapa - almacenarlo en una variable temporal, acceder al primer elemento en el mapa invertido (por lo tanto sería su último elemento), elimine la variable de temperatura.

Aquí hay algunas buenas respuestas sobre cómo revertir una orden HashMap:

How to iterate hashmap in reverse order in Java

Si utiliza la ayuda de funciones del foro, por favor renunciar a ellos-califican :) Esperamos que esto pueda ayudar a alguien.

7

LinkedHashMap implementación actual (Java 8) realiza un seguimiento de su cola. Si el rendimiento es una preocupación y/o el mapa es de gran tamaño, puede acceder a ese campo a través de la reflexión.

Como la implementación puede cambiar, probablemente sea una buena idea tener una estrategia de respaldo también. Es posible que desee registrar algo si se lanza una excepción para que sepa que la implementación ha cambiado.

Podría verse como:

public static <K, V> Entry<K, V> getFirst(Map<K, V> map) { 
    if (map.isEmpty()) return null; 
    return map.entrySet().iterator().next(); 
} 

public static <K, V> Entry<K, V> getLast(Map<K, V> map) { 
    try { 
    if (map instanceof LinkedHashMap) return getLastViaReflection(map); 
    } catch (Exception ignore) { } 
    return getLastByIterating(map); 
} 

private static <K, V> Entry<K, V> getLastByIterating(Map<K, V> map) { 
    Entry<K, V> last = null; 
    for (Entry<K, V> e : map.entrySet()) last = e; 
    return last; 
} 

private static <K, V> Entry<K, V> getLastViaReflection(Map<K, V> map) throws NoSuchFieldException, IllegalAccessException { 
    Field tail = map.getClass().getDeclaredField("tail"); 
    tail.setAccessible(true); 
    return (Entry<K, V>) tail.get(map); 
} 
+1

Creo que agregaría 'ClassCastException' al' catch' solo en caso de 'tail' no es una 'Entrada' en una subclase (o una implementación futura). –

+0

@PaulBoddington Lo he reemplazado por un todo: no se recomienda en general, pero probablemente sea apropiado aquí. – assylias

+2

ahh la respuesta "sucia" :) – rogerdpack

2

Es un poco sucio, pero se puede reemplazar el método removeEldestEntry de LinkedHashMap, que podría adaptarse a usted a hacer como miembro anónima privada:

private Splat eldest = null; 
private LinkedHashMap<Integer, Splat> pastFutures = new LinkedHashMap<Integer, Splat>() { 

    @Override 
    protected boolean removeEldestEntry(Map.Entry<Integer, Splat> eldest) { 

     eldest = eldest.getValue(); 
     return false; 
    } 
}; 

Para que siempre pueda obtener la primera entrada en su miembro eldest. Se actualizará cada vez que realice un put.

También debe ser fácil de anular put y establecer youngest ...

@Override 
    public Splat put(Integer key, Splat value) { 

     youngest = value; 
     return super.put(key, value); 
    } 

todo se derrumba cuando se inicia la eliminación de entradas sin embargo; no he encontrado una manera de evitar eso.

Es muy molesto que de otro modo no se puede obtener acceso a la cabeza o la cola de una manera sensata ...

+0

Buen intento, pero la entrada más antigua se actualiza cuando se invoca map.get. Por lo tanto, eldestEntry no se actualizará en esos casos, a menos que se llame a put después de eso. – vishr

1

Sugerencia:

map.remove(map.keySet().iterator().next()); 
9

Se puede tratar de hacer algo como (para conseguir la última entrada):

linkedHashMap.entrySet().toArray()[linkedHashMap.size() -1]; 

es O (N) :)

+2

Todavía es más rápido repetir y conservar el último elemento. 'T last = null; para (elemento T: linkedHashMap.values ​​()) last = item; 'O algo así. Es O (N) en el tiempo pero O (1) en la memoria. –