web-dev-qa-db-fra.com

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

J'ai utilisé LinkedHashMap car il est important de noter 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 la dernière.

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

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

Merci!

106
maiky

La sémantique de LinkedHashMapreste celle d'une carte, plutôt que celle d'un LinkedListname__. Oui, il conserve l'ordre d'insertion, mais il s'agit d'un détail d'implémentation plutôt que d'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 il faudra parcourir l'ensemble de l'entrée en appelant .next() jusqu'à atteindre la dernière. while (iterator.hasNext()) { lastElement = iterator.next() }

edit : Toutefois, si vous souhaitez aller au-delà de l'API JavaSE, Apache Commons Collections a le sien LinkedMapNAME _ implémentation, qui a des méthodes comme firstKeyNAME _ et lastKeyNAME _ , qui font ce que vous recherchez. L'interface est considérablement plus riche.

139
skaffman

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

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

c'est O(N) :)

15
FRR

LinkedHashMap l'implémentation actuelle (Java 8) garde une trace de sa queue. Si les performances sont préoccupantes et/ou si 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 de recourir également à une stratégie de repli. 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);
}
9
assylias

Je sais que je suis arrivé trop tard mais je voudrais proposer quelques alternatives, pas quelque chose d'extraordinaire mais quelques cas qui ne sont pas mentionnés ici. Au cas où quelqu'un se soucierait moins de l'efficacité, mais qu'il souhaite quelque chose de plus simple (peut-être trouver la dernière valeur d'entrée avec une seule ligne de code), tout cela sera simplifié avec l'arrivée de Java 8 . Je propose des scénarios utiles. 

Par souci d'exhaustivité, je compare ces alternatives à la solution de tableaux déjà mentionnée dans ce message par d'autres utilisateurs. Je résume tous les cas et je pense qu'ils seraient utiles (que la performance compte ou non), en particulier pour les nouveaux développeurs, dépend toujours de la question de chaque problème

Alternatives possibles

Utilisation de la méthode array

Je me suis inspiré 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

Semblable à la première solution avec des performances légèrement 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 d'éléments jusqu'à obtenir le dernier élément de 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();
    }

Méthode SkipFunction

Cette méthode obtiendra le dernier élément du flux simplement en sautant tous les éléments précédents.

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

Alternative Iterable

Iterables.getLast de Google Guava. Il a une optimisation pour Listes et SortedSets aussi

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
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 que parcourir les entrées de l'ensemble d'entrées et obtenir les première et dernière entrées est une meilleure approche. 

L'utilisation de méthodes de tableau conduit à l'avertissement du formulaire "... nécessite une conversion non vérifiée pour se conformer à ..."  qui ne peut pas être corrigé [mais ne peut être supprimé qu'en utilisant l'annotation @SuppressWarnings ("dé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
 </ pre> 

</ code> 

6
sateesh

C'est un peu sale, mais vous pouvez remplacer la méthode removeEldestEntry de LinkedHashMap, ce qui vous conviendra peut-être comme membre anonyme anonyme:

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 sur votre membre eldest. Il sera mis à jour chaque fois que vous effectuez une put.

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

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

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

Tout se passe lorsque vous commencez à supprimer des entrées; Je n'ai pas trouvé le moyen de minimiser cela.

C'est très énervant que vous ne puissiez pas autrement accéder à la tête ou à la queue de façon sensée ...

3
robert

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

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

1
Doua Beri

Suggestion:

map.remove(map.keySet().iterator().next());
1
Tadeu Jr.

à 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 la renvoyer.

0
unknownwill

Oui, j'ai rencontré 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 chercherais à inverser l'ordre de votre carte - enregistrez-le dans une variable temporaire, accédez au premier élément de la carte inversée (donc ce serait votre dernier élément) variable temp. 

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, accordez-leur un vote positif:) J'espère que cela pourra aider quelqu'un.

0
ryvianstyron

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

Mais c'est assez trivial pour obtenir:

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

utilise maintenant iterator sur al object; vous pouvez obtenir n'importe quel objet.

0
rai.skumar