Java LinkedHashMap ottiene la prima o l'ultima voce


139

Ho usato LinkedHashMapperché è importante l'ordine in cui le chiavi sono state inserite nella mappa.

Ma ora voglio ottenere il valore della chiave in primo luogo (la prima voce immessa) o l'ultima.

Ci dovrebbe essere un metodo come first()e last()o qualcosa del genere?

Devo avere un iteratore per ottenere la prima voce chiave? Ecco perché l'ho usato LinkedHashMap!

Grazie!


3
La situazione è davvero sfortunata. Ecco la richiesta di funzionalità (a bassa priorità) che fornirà ciò di cui hai bisogno: bugs.sun.com/view_bug.do?bug_id=6266354
Kevin Bourrillion il

Risposte:


159

La semantica di LinkedHashMapè ancora quella di una mappa, piuttosto che quella di a LinkedList. Mantiene l'ordine di inserimento, sì, ma è un dettaglio di implementazione, piuttosto che un aspetto della sua interfaccia.

Il modo più rapido per ottenere la "prima" voce è ancora entrySet().iterator().next(). Ottenere la "ultima" voce è possibile, ma comporta l'iterazione dell'intera voce impostata chiamando .next()fino a quando non si raggiunge l'ultima. while (iterator.hasNext()) { lastElement = iterator.next() }

modifica : Tuttavia, se sei disposto ad andare oltre l'API JavaSE, Apache Commons Collections ha una sua LinkedMapimplementazione, che ha metodi come firstKeye lastKey, che fanno quello che stai cercando. L'interfaccia è notevolmente più ricca.


Devo inserire la voce (chiave, valore) come prima voce nella mia mappa collegata; O qualcosa come l'inserimento basato sull'indice. è possibile con le raccolte Apache?
Kanagavelu Sugumar,

2
I link "LinkedMap", "firstKey" e "lastKey" sono stantii, a proposito.
Josh,

Sembra che in realtà tu possa persino usare la Commons LinkedMap per attraversarla in ordine inverso, ottenendo lastKey e poi usando precedentiKey per fare un passo indietro ... bello.
rogerdpack,

Sembra che in realtà tu possa persino usare la Commons LinkedMap per attraversarla in ordine inverso, ottenendo lastKey e poi usando precedentiKey per fare un passo indietro ... bello. Si potrebbe allora anche scrivere il proprio Iterable se si voleva usarlo in foreach loop es: stackoverflow.com/a/1098153/32453
rogerdpack

1
@skaffman, potresti aiutarmi: cos'è la mylinkedmap.entrySet().iterator().next()complessità del tempo? È O (1)?
tkrishtop,

25

Puoi provare a fare qualcosa del genere (per ottenere l'ultima voce):

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

5
È ancora più veloce iterare e conservare l'ultimo elemento. T last = null ; for( T item : linkedHashMap.values() ) last = item; O qualcosa di simile. È O (N) nel tempo ma O (1) in memoria.
Florian F,

@FlorianF Quindi dipende da quanto è grande il tuo elenco. La soluzione array sarebbe più veloce e non comprometterebbe la memoria se la raccolta non fosse così grande, altrimenti è meglio impiegare più tempo per iterare attraverso di essa ... Mi chiedo se ci sia un po 'di entrambi come soluzione, specialmente da Java 8.
skinny_jones,

1
@skinny_jones: perché la soluzione array dovrebbe essere più veloce? Implica ancora l'iterazione su tutta la mappa, è solo che ora l'iterazione è all'interno di un metodo JDK anziché esplicito.
Ruakh

18

So che sono arrivato troppo tardi, ma vorrei offrire alcune alternative, non qualcosa di straordinario, ma alcuni casi che nessuno ha menzionato qui. Nel caso in cui qualcuno non si preoccupi così tanto dell'efficienza ma vuole qualcosa con più semplicità (forse trova l'ultimo valore della voce con una riga di codice), tutto questo sarà abbastanza semplificato con l'arrivo di Java 8 . Fornisco alcuni scenari utili.

Per completezza, confronto queste alternative con la soluzione di array già menzionata in questo post da altri utenti. Riassumo tutti i casi e penso che sarebbero utili (quando le prestazioni contano o no) soprattutto per i nuovi sviluppatori, dipende sempre dalla questione di ogni problema

Possibili alternative

Utilizzo del metodo array

L'ho preso dalla risposta precedente per fare i seguenti confronti. Questa soluzione appartiene a @feresr.

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

Utilizzo del metodo ArrayList

Simile alla prima soluzione con prestazioni leggermente diverse

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

Metodo di riduzione

Questo metodo ridurrà il set di elementi fino a ottenere l'ultimo elemento del flusso. Inoltre, restituirà solo risultati deterministici

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

Metodo SkipFunction

Questo metodo otterrà l'ultimo elemento del flusso semplicemente saltando tutti gli elementi prima di esso

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

Alternativa Iterable

Iterables.getUltimo da Google Guava. Ha alcune ottimizzazioni anche per Elenchi e SortedSet

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

Ecco il codice sorgente 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]);
    }
}

Ecco l'output con le prestazioni di ciascun metodo

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

LinkedHashMapl'attuale implementazione (Java 8) tiene traccia della sua coda. Se le prestazioni sono un problema e / o la mappa è di grandi dimensioni, è possibile accedere a quel campo tramite la riflessione.

Poiché l'implementazione può cambiare, è probabilmente una buona idea avere anche una strategia di fallback. Potresti voler registrare qualcosa se viene generata un'eccezione, quindi sai che l'implementazione è cambiata.

Potrebbe apparire come:

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
Penso che aggiungerei ClassCastExceptional catchcaso in cui tailnon sia Entryuna sottoclasse (o un'implementazione futura).
Paul Boddington,

@PaulBoddington L'ho sostituito con un fermo tutto - non raccomandato in generale ma probabilmente appropriato qui.
Assylias,

7
ahh la risposta "sporca" :)
rogerdpack il

6

Un altro modo per ottenere la prima e l'ultima voce di una LinkedHashMap è utilizzare il metodo "toArray" dell'interfaccia Set.

Ma penso che scorrere le voci nel set di voci e ottenere la prima e l'ultima voce sia un approccio migliore.

L'uso di metodi array porta all'avvertimento del modulo "... necessita di conversione non selezionata per conformarsi a ..." che non può essere risolto [ma può essere soppresso solo usando l'annotazione @SuppressWarnings ("unchecked")].

Ecco un piccolo esempio per dimostrare l'utilizzo del metodo "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
lo stesso metodo toArray ripeterà nuovamente l'hashmap :) Quindi è piuttosto inefficiente a causa dei pochi cicli extra sprecati nella creazione dell'array e dello spazio allocato per l'array.
Durin,

5

È un po 'sporco, ma puoi ignorare il removeEldestEntrymetodo di LinkedHashMap, che potrebbe essere adatto a te fare come membro anonimo privato:

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

Quindi sarai sempre in grado di ottenere la prima voce sul tuo eldestmembro. Sarà aggiornato ogni volta che esegui un put.

Dovrebbe anche essere facile ignorare pute impostare youngest...

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

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

Tutto si interrompe quando si inizia a rimuovere le voci; non ho trovato il modo di farlo.

È molto fastidioso che altrimenti non si possa accedere in modo sensato alla testa o alla coda ...


Buona prova, ma la voce più anziana viene aggiornata quando viene invocato map.get. Quindi eldestEntry non verrà aggiornato in questi casi, a meno che non venga chiamato put.
vishr

La voce più anziana di @vishr viene aggiornata quando viene invocato put. (ottieni e metti per l'ordine di accesso LinkedHashMap)
Shiji.J

2

Forse qualcosa del genere:

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

Suggerimento:

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

fornisce la prima chiave inserita nella mappa. Trovare la prima chiave sarà in O (1). Il problema qui è trovare un modo in O (1) per trovare l'ultima chiave inserita.
Emad Aghayi,

1

Consiglierei l'uso di ConcurrentSkipListMap che ha firstKey()e lastKey()metodi


1
ConcurrentSkipListMap richiede un comparatore (o un comparatore naturale), quindi è necessario fare un lavoro extra per salvare l'ordine in cui vengono inserite le voci.
AlikElzin-Kilaka,

HashMap e in particolare LinkedHashMap forniscono l'accesso in media a O (1), al contrario di SkipList, che fornisce l'accesso in media a O (logn).
AlikElzin-Kilaka,

"La mappa è ordinata in base all'ordinamento naturale delle sue chiavi"

1

Per il primo elemento utilizzare entrySet().iterator().next()e interrompere l'iterazione dopo 1 iterazione. Per ultimo, il modo più semplice è conservare la chiave in una variabile ogni volta che si esegue un map.put.


0

Anche se LinkedHashMap non fornisce alcun metodo per ottenere il primo, l'ultimo o qualsiasi oggetto specifico.

Ma è piuttosto banale ottenere:

  • Mappa orderMap = new LinkedHashMap ();
    Set al = orderMap.keySet ();

ora usando iteratore su tutti gli oggetti; puoi ottenere qualsiasi oggetto.


0

Sì, mi sono imbattuto nello stesso problema, ma per fortuna ho solo bisogno del primo elemento ... - Questo è quello che ho fatto per questo.

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

Se hai bisogno anche dell'ultimo elemento - esaminerei come invertire l'ordine della tua mappa - memorizzalo in una variabile temporanea, accedi al primo elemento nella mappa invertita (quindi sarebbe il tuo ultimo elemento), uccidi il variabile temp.

Ecco alcune buone risposte su come invertire un hashmap:

Come iterare l'hashmap in ordine inverso in Java

Se usi l'aiuto del link sopra, ti preghiamo di dare loro voti positivi :) Spero che questo possa aiutare qualcuno.


0

giusto, devi enumerare manualmente il keyset fino alla fine dell'elenco collegato, quindi recuperare la voce per chiave e restituire questa voce.


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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.