Java LinkedHashMap obtient la première ou la dernière entrée

139

J'ai utilisé LinkedHashMapcar il est important de l'ordre dans lequel les clés sont entrées dans la carte.

Mais maintenant, je veux obtenir la valeur de la clé en premier lieu (la première entrée entrée) ou en dernier.

Devrait-il y avoir une méthode comme first()et last()ou quelque chose comme ça?

Dois-je avoir un itérateur pour obtenir simplement la première entrée de clé? C'est pourquoi j'ai utilisé LinkedHashMap!

Merci!

maiky
la source
3
La situation est en effet malheureuse. Voici la demande de fonctionnalité (basse priorité) qui fournirait ce dont vous avez besoin: bugs.sun.com/view_bug.do?bug_id=6266354
Kevin Bourrillion
stackoverflow.com/a/30984049/1808829 pourrait vous aider
Ayaz Alifov

Réponses:

159

La sémantique de LinkedHashMapest toujours celle d'une Map, plutôt que celle de a LinkedList. Il conserve l'ordre d'insertion, oui, mais c'est un détail d'implémentation, plutôt qu'un aspect de son interface.

Le moyen le plus rapide d'obtenir la "première" entrée est toujours entrySet().iterator().next(). Obtenir la «dernière» entrée est possible, mais impliquera une itération sur tout le jeu d'entrées en appelant .next()jusqu'à ce que vous atteigniez la dernière. while (iterator.hasNext()) { lastElement = iterator.next() }

edit : Cependant, si vous êtes prêt à aller au-delà de l'API JavaSE, Apache Commons Collections a sa propre LinkedMapimplémentation, qui a des méthodes comme firstKeyet lastKey, qui font ce que vous recherchez. L'interface est considérablement plus riche.

skaffman
la source
Je dois insérer une entrée (clé, valeur) comme première entrée dans ma carte liée; OU quelque chose comme l'insertion basée sur un index. est-ce possible avec les collections Apache?
Kanagavelu Sugumar
2
Les liens «LinkedMap», «firstKey» et «lastKey» sont périmés, pour info.
Josh
Il semble que vous pouvez peut-être même utiliser Commons LinkedMap pour le parcourir dans l'ordre inverse, en obtenant lastKey puis en utilisant previousKey pour revenir en arrière ... bien.
rogerdpack
Il semble que vous puissiez peut-être même utiliser Commons LinkedMap pour le parcourir dans l'ordre inverse, en obtenant lastKey puis en utilisant previousKey pour revenir en arrière ... bien. Vous pourriez même écrire votre propre Iterable si vous vouliez l'utiliser dans les boucles foreach ex: stackoverflow.com/a/1098153/32453
rogerdpack
1
@skaffman, pourriez-vous s'il vous plaît aider: qu'est-ce que la mylinkedmap.entrySet().iterator().next()complexité temporelle? Est-ce O (1)?
tkrishtop
25

Pouvez-vous essayer de faire quelque chose comme (pour obtenir la dernière entrée):

linkedHashMap.entrySet().toArray()[linkedHashMap.size() -1];
FRR
la source
5
Il est toujours plus rapide d'itérer et de conserver le dernier élément. T last = null ; for( T item : linkedHashMap.values() ) last = item; Ou quelque chose comme ça. C'est O (N) dans le temps mais O (1) en mémoire.
Florian F
@FlorianF Cela dépend donc de la taille de votre liste. La solution de tableau serait plus rapide et ne compromettrait pas la mémoire si la collection n'est pas si grande, sinon il vaut mieux prendre plus de temps pour l'itérer ... Je me demande s'il existe un peu des deux comme solution, surtout depuis Java 8.
skinny_jones
1
@skinny_jones: Pourquoi la solution de baie serait-elle plus rapide? Cela implique toujours une itération sur toute la carte, c'est juste que maintenant l'itération est à l'intérieur d'une méthode JDK au lieu d'être explicite.
ruakh
18

Je sais que je suis arrivé trop tard mais je voudrais proposer des alternatives, pas quelque chose d'extraordinaire mais des cas qu'aucun d'entre eux n'a mentionnés ici. Dans le cas où quelqu'un ne se soucie pas tellement de l'efficacité mais qu'il veut quelque chose avec plus de simplicité (peut-être trouver la dernière valeur d'entrée avec une ligne de code), tout cela sera assez simplifié avec l'arrivée de Java 8 . Je propose quelques scénarios utiles.

Par souci d'exhaustivité, je compare ces alternatives avec la solution de tableaux déjà mentionnée dans cet article par d'autres utilisateurs. Je résume tous les cas et je pense qu'ils seraient utiles (quand les performances comptent ou pas) surtout pour les nouveaux développeurs, cela dépend toujours de la question de chaque problème

Alternatives possibles

Utilisation de la méthode Array

Je l'ai pris de la réponse précédente pour faire les comparaisons suivantes. Cette solution appartient à @feresr.

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

Utilisation de la méthode ArrayList

Similaire à la première solution avec des performances un peu différentes

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

Méthode de réduction

Cette méthode réduira l'ensemble des éléments jusqu'à obtenir le dernier élément du flux. De plus, il ne retournera que des résultats déterministes

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

SkipFunction, méthode

Cette méthode obtiendra le dernier élément du flux en sautant simplement tous les éléments avant

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

Alternative itérable

Iterables.getLast de Google Guava. Il a également une certaine optimisation pour les listes et les ensembles triés

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

Voici le code source complet

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

Voici la sortie avec les performances de chaque méthode

FindLasstEntryWithArrayListMethod : took 0.162 milliseconds
FindLasstEntryWithArrayMethod : took 0.025 milliseconds
FindLasstEntryWithReduceMethod : took 2.776 milliseconds
FindLasstEntryWithSkipFunctionMethod : took 3.396 milliseconds
FindLasstEntryWithGuavaIterable : took 11 milliseconds
Panagiotis Drakatos
la source
11

LinkedHashMapl'implémentation actuelle (Java 8) garde une trace de sa queue. Si les performances sont un problème et / ou que la carte est de grande taille, vous pouvez accéder à ce champ par réflexion.

Étant donné que la mise en œuvre peut changer, il est probablement judicieux d'avoir également une stratégie de secours. Vous souhaiterez peut-être enregistrer quelque chose si une exception est levée afin de savoir que l'implémentation a changé.

Cela pourrait ressembler à:

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);
}
assylies
la source
1
Je pense que j'ajouterais ClassCastExceptionau catchcas où ce tailn'est pas une Entrysous-classe (ou une implémentation future).
Paul Boddington
@PaulBoddington Je l'ai remplacé par un fourre-tout - pas recommandé en général mais probablement approprié ici.
assylias
7
ahh la réponse "sale" :)
rogerdpack
6

Une autre façon d'obtenir la première et la dernière entrée d'un LinkedHashMap est d'utiliser la méthode "toArray" de l'interface Set.

Mais je pense qu'itérer les entrées du jeu d'entrées et obtenir la première et la dernière entrée est une meilleure approche.

L'utilisation des méthodes de tableau conduit à un avertissement de la forme "... nécessite une conversion non cochée pour se conformer à ..." qui ne peut pas être corrigé [mais ne peut être supprimé qu'en utilisant l'annotation @SuppressWarnings ("non cochée")].

Voici un petit exemple pour illustrer l'utilisation de la méthode "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

satin
la source
4
la méthode toArray elle-même effectuera à nouveau une itération sur le hashmap :) Donc, c'est un peu plus inefficace à cause des quelques cycles supplémentaires gaspillés dans la création du tableau et de l'espace alloué pour le tableau.
Durin
5

C'est un peu sale, mais vous pouvez remplacer la removeEldestEntryméthode de LinkedHashMap, ce qu'elle pourrait vous convenir de faire en tant que membre anonyme privé:

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

Ainsi, vous pourrez toujours obtenir la première entrée chez votre eldestmembre. Il sera mis à jour chaque fois que vous effectuez unput .

Il devrait également être facile de remplacer putet de définir youngest...

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

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

Tout cela tombe en panne lorsque vous commencez à supprimer des entrées; Je n'ai pas trouvé un moyen de dissiper cela.

C'est très ennuyeux que vous ne puissiez pas autrement accéder à la tête ou à la queue de manière sensée ...

Robert
la source
Bien essayé, mais l'entrée la plus ancienne est mise à jour lorsque map.get est appelé. Ainsi eldestEntry ne sera pas mis à jour dans ces cas, sauf si put est appelé après cela.
vishr
L'entrée la plus ancienne @vishr est mise à jour lorsque put est appelé. (obtenir et mettre pour ordre d'accès LinkedHashMap)
Shiji.J
2

Peut-être quelque chose comme ça:

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;
}
user2127649
la source
2

Suggestion:

map.remove(map.keySet().iterator().next());
Tadeu Jr.
la source
il donne la première clé insérée dans la carte. Trouver la première clé se fera en O (1). Le problème ici est de trouver un moyen dans O (1) pour trouver la dernière clé insérée.
Emad Aghayi le
1

Je recommanderais d'utiliser ConcurrentSkipListMap qui a firstKey()et lastKey()méthodes

Doua Beri
la source
1
ConcurrentSkipListMap nécessite un comparateur (ou comparateur naturel), vous devez donc faire un travail supplémentaire pour enregistrer l'ordre dans lequel les entrées sont placées.
AlikElzin-kilaka
HashMap et plus précisément LinkedHashMap permet d'accéder en moyenne à O (1) - contrairement à une SkipList, qui permet d'accéder en moyenne à O (logn).
AlikElzin-kilaka
"La carte est triée selon l'ordre naturel de ses clés"
1

Pour le premier élément, utilisez entrySet().iterator().next()et arrêtez l'itération après 1 itération. Pour le dernier, le moyen le plus simple est de conserver la clé dans une variable chaque fois que vous effectuez un map.put.

Arnab
la source
0

Bien que linkedHashMap ne fournisse aucune méthode pour obtenir le premier, le dernier ou un objet spécifique.

Mais c'est assez trivial à obtenir:

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

utilisant maintenant l'itérateur sur un objet; vous pouvez obtenir n'importe quel objet.

rai.skumar
la source
0

Oui je suis tombé sur le même problème, mais heureusement je n'ai besoin que du premier élément ... - C'est ce que j'ai fait pour cela.

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

Si vous avez également besoin du dernier élément - je vais voir comment inverser l'ordre de votre carte - stockez-le dans une variable temporaire, accédez au premier élément de la carte inversée (ce serait donc votre dernier élément), tuez le variable de température.

Voici quelques bonnes réponses sur la façon d'inverser l'ordre d'un hashmap:

Comment itérer hashmap dans l'ordre inverse en Java

Si vous utilisez l'aide du lien ci-dessus, veuillez leur donner des votes positifs :) J'espère que cela peut aider quelqu'un.

ryvianstyron
la source
0

à droite, vous devez énumérer manuellement le jeu de clés jusqu'à la fin de la liste liée, puis récupérer l'entrée par clé et renvoyer cette entrée.

inconnu
la source
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;
}
Владислав Шестернин
la source