Java LinkedHashMap obtém a primeira ou a última entrada


138

Eu usei LinkedHashMapporque é importante a ordem em que as chaves foram inseridas no mapa.

Mas agora quero obter o valor da chave em primeiro lugar (a primeira entrada inserida) ou a última.

Deveria haver um método como first()e last()ou algo assim?

Preciso ter um iterador para obter apenas a primeira entrada de chave? Por isso eu usei LinkedHashMap!

Obrigado!


3
A situação é realmente infeliz. Aqui é o (baixa prioridade) pedido de recurso que iria fornecer o que você precisa: bugs.sun.com/view_bug.do?bug_id=6266354
Kevin Bourrillion

Respostas:


158

A semântica de LinkedHashMapainda é a de um mapa, e não a de a LinkedList. Ele mantém a ordem de inserção, sim, mas isso é um detalhe de implementação, e não um aspecto de sua interface.

A maneira mais rápida de obter a "primeira" entrada ainda é entrySet().iterator().next(). É possível obter a "última" entrada, mas isso implica na iteração de toda a entrada definida, ligando .next()até você chegar à última. while (iterator.hasNext()) { lastElement = iterator.next() }

edit : No entanto, se você estiver disposto a ir além da API do JavaSE, o Apache Commons Collections terá sua própria LinkedMapimplementação, que possui métodos como firstKeye lastKey, que fazem o que você está procurando. A interface é consideravelmente mais rica.


Eu tenho que inserir a entrada (chave, valor) como uma primeira entrada no meu mapa vinculado; OU algo como inserção baseada em índice. isso é possível com as coleções do Apache?
Kanagavelu Sugumar

2
Os links "LinkedMap", "firstKey" e "lastKey" estão obsoletos.
Josh

Parece que você realmente pode até usar o Commons LinkedMap para percorrê-lo em ordem inversa, obtendo lastKey e depois previousKey depois para retroceder ... bom.
Rogerdpack

Parece que você pode até usar o Commons LinkedMap para percorrê-lo na ordem inversa, obtendo lastKey e depois previousKey depois para retroceder ... bom. Você poderia, então, até mesmo escrever seu próprio Iterable se você quiser usá-lo em foreach loops de ex: stackoverflow.com/a/1098153/32453
rogerdpack

1
@skaffman, você poderia ajudar: o que é mylinkedmap.entrySet().iterator().next()complexidade de tempo? É O (1)?
tkrishtop

25

Você pode tentar fazer algo como (para obter a última entrada):

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

5
Ainda é mais rápido iterar e manter o último item. T last = null ; for( T item : linkedHashMap.values() ) last = item; Ou algo assim. É O (N) no tempo, mas O (1) na memória.
Florian F

@FlorianF Portanto, depende do tamanho da sua lista. A solução de matriz seria mais rápida e não comprometeria a memória se a coleção não fosse tão grande; caso contrário, é melhor levar mais tempo para iterá-la ... Gostaria de saber se há um pouco de ambas como solução, especialmente desde o Java 8.
skinny_jones 16/05/19

1
@skinny_jones: Por que a solução de matriz seria mais rápida? Ainda envolve a iteração em todo o mapa, mas agora a iteração está dentro de um método JDK em vez de explícito.
Ruakh19

17

Sei que cheguei tarde demais, mas gostaria de oferecer algumas alternativas, não algo extraordinário, mas alguns casos que nenhum deles mencionou aqui. Caso alguém não se importe tanto com eficiência, mas queira algo com mais simplicidade (talvez encontre o último valor de entrada com uma linha de código), tudo isso será bastante simplificado com a chegada do Java 8 . Eu forneço alguns cenários úteis.

Por uma questão de integridade, comparo essas alternativas com a solução de matrizes que já foram mencionadas neste post por outros usuários. Resumo todos os casos e acho que seriam úteis (quando o desempenho é importante ou não), especialmente para novos desenvolvedores, sempre depende da questão de cada problema

Alternativas possíveis

Uso do método Array

Peguei na resposta anterior para fazer as comparações a seguir. Esta solução pertence a @feresr.

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

Uso do método ArrayList

Semelhante à primeira solução com desempenho um pouco 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();
    }

Método de redução

Este método reduzirá o conjunto de elementos até obter o último elemento do fluxo. Além disso, ele retornará apenas resultados determinísticos

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

Método SkipFunction

Esse método obterá o último elemento do fluxo simplesmente ignorando todos os elementos antes dele

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

Alternativa Iterável

Iterables.getÚltimo do Google Guava. Tem alguma otimização para Lists e SortedSets também

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

Aqui está o código fonte 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]);
    }
}

Aqui está a saída com desempenho 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

LinkedHashMapA implementação atual (Java 8) mantém o controle de sua cauda. Se o desempenho for uma preocupação e / ou o mapa for grande em tamanho, você poderá acessar esse campo por meio de reflexão.

Como a implementação pode mudar, é provavelmente uma boa ideia também ter uma estratégia de fallback. Convém registrar algo se uma exceção for lançada, para que você saiba que a implementação foi alterada.

Pode parecer com:

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
Eu acho que eu adicionaria ClassCastExceptionao catchjust in case tailnão é Entryuma subclasse (ou uma implementação futura).
Paul Boddington

@PaulBoddington Substituí-o por um problema geral - não recomendado em geral, mas provavelmente apropriado aqui.
assylias

7
ahh a resposta "suja" :)
rogerdpack

6

Mais uma maneira de obter a primeira e a última entrada de um LinkedHashMap é usar o método "toArray" da interface Set.

Mas acho que iterar sobre as entradas no conjunto de entradas e obter a primeira e a última entrada é uma abordagem melhor.

O uso de métodos de matriz leva ao aviso do formulário "... precisa de conversão desmarcada para se ajustar a ..." que não pode ser corrigido [mas só pode ser suprimido usando a anotação @SuppressWarnings ("desmarcada")].

Aqui está um pequeno exemplo para demonstrar o uso do 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


4
o próprio método toArray repetirá o hashmap novamente :) Portanto, é mais ineficiente por causa dos poucos ciclos extras desperdiçados na criação da matriz e do espaço alocado para a matriz.
Durin 29/05

5

Está um pouco sujo, mas você pode substituir o removeEldestEntrymétodo do LinkedHashMap, que pode ser adequado para você como um membro 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;
    }
};

Assim, você sempre poderá obter a primeira entrada no seu eldestmembro. Será atualizado sempre que você executar um put.

Também deve ser fácil substituir pute definir youngest...

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

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

Tudo falha quando você começa a remover as entradas; não descobri uma maneira de julgar isso.

É muito chato que você não possa ter acesso à cabeça ou à cauda de uma maneira sensata ...


Boa tentativa, mas a entrada mais antiga é atualizada quando map.get é chamado. Portanto, eldestEntry não será atualizado nesses casos, a menos que put seja chamado depois disso.
vishr

A entrada mais antiga do @vishr é atualizada quando o put é chamado. (obter e colocar para LinkedHashMap ordem de acesso)
Shiji.J

2

Talvez algo parecido com isto:

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

Sugestão:

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

fornece a primeira chave inserida no mapa. A localização da primeira chave estará em O (1). O problema aqui é encontrar uma maneira em O (1) para encontrar a última chave inserida.
Emad Aghayi 24/04

1

Eu recomendaria usar ConcurrentSkipListMap que tem firstKey()e lastKey()métodos


1
ConcurrentSkipListMap requer um comparador (ou comparador natural), portanto, você precisa fazer um trabalho extra para salvar a ordem em que as entradas são colocadas.
AlikElzin-kilaka 23/09/12

O HashMap e especificamente o LinkedHashMap fornecem acesso em média O (1) - ao contrário de um SkipList, que fornece acesso em média O (logn).
AlikElzin-kilaka 23/09/12

"O mapa está classificado de acordo com a ordem natural de suas chaves"

1

Para o primeiro elemento, use entrySet().iterator().next()e pare de iterar após 1 iteração. Para a última, a maneira mais fácil é preservar a chave em uma variável sempre que você fizer um map.put.


0

Embora linkedHashMap não forneça nenhum método para obter o primeiro, o último ou qualquer objeto específico.

Mas é bastante trivial obter:

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

agora usando o iterador em um objeto; você pode obter qualquer objeto.


0

Sim, me deparei com o mesmo problema, mas, felizmente, só preciso do primeiro elemento ... - Foi o que fiz para isso.

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

Se você também precisar do último elemento - eu verificaria como reverter a ordem do seu mapa - armazene-o em uma variável temp, acesse o primeiro elemento no mapa invertido (portanto, seria o seu último elemento), mate o variável temp.

Aqui estão algumas boas respostas sobre como reverter a ordem de um hashmap:

Como iterar o hashmap na ordem inversa em Java

Se você usar a ajuda do link acima, dê votos a eles :) Espero que isso ajude alguém.


0

à direita, você deve enumerar manualmente o conjunto de chaves até o final da lista vinculada, recuperar a entrada por chave e retornar essa 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;
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.