Java LinkedHashMap obtiene primera o última entrada


139

Lo he usado LinkedHashMapporque es importante el orden en que las claves ingresaron en el mapa.

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 clave? ¡Por eso solía LinkedHashMap!

¡Gracias!


3
La situación es realmente lamentable. Aquí está la solicitud de función (de baja prioridad) que proporcionaría lo que necesita: bugs.sun.com/view_bug.do?bug_id=6266354
Kevin Bourrillion

Respuestas:


159

La semántica de LinkedHashMapsigue siendo la de un Mapa, en lugar de la de un LinkedList. Conserva el orden de inserción, sí, pero ese es un detalle de implementación, más que un aspecto de su interfaz.

La forma más rápida de obtener la "primera" entrada sigue siendo entrySet().iterator().next(). Es posible obtener la "última" entrada, pero implicará iterar sobre todo el conjunto de entradas llamando .next()hasta llegar a la última. while (iterator.hasNext()) { lastElement = iterator.next() }

editar : Sin embargo, si está dispuesto a ir más allá de la API JavaSE, Apache Commons Collections tiene su propia LinkedMapimplementación, que tiene métodos como firstKeyy lastKey, que hacen lo que está buscando. La interfaz es considerablemente más rica.


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

2
Los enlaces "LinkedMap", "firstKey" y "lastKey" son obsoletos, para su información.
Josh

Parece que en realidad quizás incluso puedas usar el LinkedMap de Commons para recorrerlo en orden inverso, obteniendo lastKey y luego anteriorKey para retroceder ... bueno.
rogerdpack

Parece que en realidad quizás incluso puedas usar el LinkedMap de Commons para recorrerlo en orden inverso, obteniendo lastKey y luego anteriorKey para retroceder ... bueno. Incluso podría escribir su propio Iterable si quisiera usarlo en bucles foreach, por ejemplo: stackoverflow.com/a/1098153/32453
rogerdpack

1
@skaffman, ¿podría ayudarme: qué es la mylinkedmap.entrySet().iterator().next()complejidad del tiempo? ¿Es O (1)?
tkrishtop

25

¿Puedes intentar hacer algo como (para obtener la última entrada):

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

55
Todavía es más rápido iterar y conservar el último elemento. T last = null ; for( T item : linkedHashMap.values() ) last = item; O algo así. Es O (N) en el tiempo pero O (1) en la memoria.
Florian F

@FlorianF Entonces, depende de qué tan grande sea su lista. La solución de matriz sería más rápida y no comprometería la memoria si la colección no es tan grande, de lo contrario, es mejor tomar más tiempo iterar a través de ella ... Me pregunto si hay un poco de ambas como solución, especialmente desde Java 8.
skinny_jones

1
@skinny_jones: ¿Por qué la solución de matriz sería más rápida? Todavía implica iterar sobre todo el mapa, es solo que ahora la iteración está dentro de un método JDK en lugar de explícito.
ruakh

18

Sé que llegué demasiado tarde, pero me gustaría ofrecer algunas alternativas, no algo extraordinario, sino algunos casos que ninguno mencionó aquí. En caso de que a alguien no le importe tanto la eficiencia pero quiera algo con más simplicidad (tal vez encuentre el último valor de entrada con una línea de código), todo esto se simplificará bastante con la llegada de Java 8 . Proporciono algunos escenarios útiles.

En aras de la exhaustividad, comparo estas alternativas con la solución de matrices que otros usuarios ya mencionaron en esta publicación. Resumo todos los casos y creo que serían útiles (cuando el rendimiento importa o no), especialmente para los nuevos desarrolladores, siempre depende de cada problema.

Posibles alternativas

Uso del método de matriz

Lo tomé de la respuesta anterior para hacer las siguientes comparaciones. Esta solución pertenece a @feresr.

  public static String FindLasstEntryWithArrayMethod() {
        return String.valueOf(linkedmap.entrySet().toArray()[linkedmap.size() - 1]);
    }

Uso del método ArrayList

Similar a la primera solución con un rendimiento un poco diferente

public static String FindLasstEntryWithArrayListMethod() {
        List<Entry<Integer, String>> entryList = new ArrayList<Map.Entry<Integer, String>>(linkedmap.entrySet());
        return entryList.get(entryList.size() - 1).getValue();
    }

Reducir método

Este método reducirá el conjunto de elementos hasta obtener el último elemento de la secuencia. Además, solo devolverá resultados deterministas

public static String FindLasstEntryWithReduceMethod() {
        return linkedmap.entrySet().stream().reduce((first, second) -> second).orElse(null).getValue();
    }

Método SkipFunction

Este método obtendrá el último elemento de la secuencia simplemente omitiendo todos los elementos antes de que

public static String FindLasstEntryWithSkipFunctionMethod() {
        final long count = linkedmap.entrySet().stream().count();
        return linkedmap.entrySet().stream().skip(count - 1).findFirst().get().getValue();
    }

Alternativa Iterable

Iterables.getLast de Google Guava. También tiene alguna optimización para listas y conjuntos ordenados

public static String FindLasstEntryWithGuavaIterable() {
        return Iterables.getLast(linkedmap.entrySet()).getValue();
    }

Aquí está el código fuente completo

import com.google.common.collect.Iterables;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class PerformanceTest {

    private static long startTime;
    private static long endTime;
    private static LinkedHashMap<Integer, String> linkedmap;

    public static void main(String[] args) {
        linkedmap = new LinkedHashMap<Integer, String>();

        linkedmap.put(12, "Chaitanya");
        linkedmap.put(2, "Rahul");
        linkedmap.put(7, "Singh");
        linkedmap.put(49, "Ajeet");
        linkedmap.put(76, "Anuj");

        //call a useless action  so that the caching occurs before the jobs starts.
        linkedmap.entrySet().forEach(x -> {});



        startTime = System.nanoTime();
        FindLasstEntryWithArrayListMethod();
        endTime = System.nanoTime();
        System.out.println("FindLasstEntryWithArrayListMethod : " + "took " + new BigDecimal((endTime - startTime) / 1000000.000).setScale(3, RoundingMode.CEILING) + " milliseconds");


         startTime = System.nanoTime();
        FindLasstEntryWithArrayMethod();
        endTime = System.nanoTime();
        System.out.println("FindLasstEntryWithArrayMethod : " + "took " + new BigDecimal((endTime - startTime) / 1000000.000).setScale(3, RoundingMode.CEILING) + " milliseconds");

        startTime = System.nanoTime();
        FindLasstEntryWithReduceMethod();
        endTime = System.nanoTime();

        System.out.println("FindLasstEntryWithReduceMethod : " + "took " + new BigDecimal((endTime - startTime) / 1000000.000).setScale(3, RoundingMode.CEILING) + " milliseconds");

        startTime = System.nanoTime();
        FindLasstEntryWithSkipFunctionMethod();
        endTime = System.nanoTime();

        System.out.println("FindLasstEntryWithSkipFunctionMethod : " + "took " + new BigDecimal((endTime - startTime) / 1000000.000).setScale(3, RoundingMode.CEILING) + " milliseconds");

        startTime = System.currentTimeMillis();
        FindLasstEntryWithGuavaIterable();
        endTime = System.currentTimeMillis();
        System.out.println("FindLasstEntryWithGuavaIterable : " + "took " + (endTime - startTime) + " milliseconds");


    }

    public static String FindLasstEntryWithReduceMethod() {
        return linkedmap.entrySet().stream().reduce((first, second) -> second).orElse(null).getValue();
    }

    public static String FindLasstEntryWithSkipFunctionMethod() {
        final long count = linkedmap.entrySet().stream().count();
        return linkedmap.entrySet().stream().skip(count - 1).findFirst().get().getValue();
    }

    public static String FindLasstEntryWithGuavaIterable() {
        return Iterables.getLast(linkedmap.entrySet()).getValue();
    }

    public static String FindLasstEntryWithArrayListMethod() {
        List<Entry<Integer, String>> entryList = new ArrayList<Map.Entry<Integer, String>>(linkedmap.entrySet());
        return entryList.get(entryList.size() - 1).getValue();
    }

    public static String FindLasstEntryWithArrayMethod() {
        return String.valueOf(linkedmap.entrySet().toArray()[linkedmap.size() - 1]);
    }
}

Aquí está la salida con el rendimiento de cada método.

FindLasstEntryWithArrayListMethod : took 0.162 milliseconds
FindLasstEntryWithArrayMethod : took 0.025 milliseconds
FindLasstEntryWithReduceMethod : took 2.776 milliseconds
FindLasstEntryWithSkipFunctionMethod : took 3.396 milliseconds
FindLasstEntryWithGuavaIterable : took 11 milliseconds

11

LinkedHashMapLa 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.

Debido a que la implementación puede cambiar, probablemente sea una buena idea tener una estrategia alternativa también. Es posible que desee registrar algo si se produce una excepción para que sepa que la implementación ha cambiado.

Podría verse así:

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 ClassCastExceptional por catchsi acaso tailno es un Entryen una subclase (o una implementación futura).
Paul Boddington

@PaulBoddington Lo he reemplazado con un catch all - no recomendado en general pero probablemente apropiado aquí.
Assylias

77
ahh la respuesta "sucia" :)
rogerdpack

6

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

Pero creo que iterar sobre las entradas en el conjunto de entradas 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 "... necesita una conversión no verificada para ajustarse a ...", que no se puede arreglar [pero solo se puede suprimir mediante la anotación @SuppressWarnings ("sin marcar")].

Aquí hay un pequeño ejemplo para demostrar el uso del método "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


44
el método toArray mismo iterará sobre el hashmap nuevamente :) 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

5

Está un poco sucio, pero puede anular el removeEldestEntrymétodo de LinkedHashMap, que podría ser adecuado para usted como miembro anónimo privado:

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

Por lo tanto, siempre podrá obtener la primera entrada en su eldestmiembro. Se actualizará cada vez que realice una put.

También debería ser fácil anular puty configurar youngest...

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

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

Sin embargo, todo se rompe cuando comienzas a eliminar entradas; no he encontrado una manera de criticar eso.

Es muy molesto que de otra manera no puedas acceder a la cabeza o la cola de una manera sensata ...


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.
Visite el

La entrada más antigua de @vishr se actualiza cuando se invoca put. (obtener y poner por orden de acceso LinkedHashMap)
Shiji.J

2

Quizás 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

Sugerencia:

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

da la primera clave insertada en el mapa. Encontrar la primera clave estará en O (1). El problema aquí es encontrar una forma en O (1) para encontrar la última clave insertada.
Emad Aghayi

1

Recomendaría usar 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 el que se colocan las entradas.
AlikElzin-kilaka

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

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

1

Para el primer elemento, use entrySet().iterator().next()y deje de iterar después de 1 iteración. Para el último, la forma más fácil es preservar la clave en una variable cada vez que haces un map.put.


0

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

Pero es bastante trivial conseguirlo:

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

ahora usando iterador en un objeto; Puedes conseguir cualquier objeto.


0

Sí, me encontré con el mismo problema, pero afortunadamente solo necesito 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 también necesita el último elemento, buscaría cómo invertir el orden de su mapa, guárdelo en una variable temporal, acceda al primer elemento en el mapa invertido (por lo tanto, sería su último elemento), elimine el variable temporal

Aquí hay algunas buenas respuestas sobre cómo revertir el orden de un hashmap:

Cómo iterar hashmap en orden inverso en Java

Si usa la ayuda del enlace de arriba, por favor denles un voto positivo :) Espero que esto pueda ayudar a alguien.


0

a la derecha, debe enumerar manualmente el conjunto de claves hasta el final de la lista vinculada, luego recuperar la entrada por clave y devolver esta entrada.


0
public static List<Fragment> pullToBackStack() {
    List<Fragment> fragments = new ArrayList<>();
    List<Map.Entry<String, Fragment>> entryList = new ArrayList<>(backMap.entrySet());
    int size = entryList.size();
    if (size > 0) {
        for (int i = size - 1; i >= 0; i--) {// last Fragments
            fragments.add(entryList.get(i).getValue());
            backMap.remove(entryList.get(i).getKey());
        }
        return fragments;
    }
    return null;
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.