183

I have used LinkedHashMap because it is important the order in which keys entered in the map.

But now I want to get the value of key in the first place (the first entered entry) or the last.

Should there be a method like first() and last() or something like that?

Do I need to have an iterator to just get the first key entry? That is why I used LinkedHashMap!

Thanks!

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
maiky
  • 3,503
  • 7
  • 28
  • 28
  • 6
    The situation is indeed unfortunate. Here is the (low-priority) feature request that would provide what you need: http://bugs.sun.com/view_bug.do?bug_id=6266354 – Kevin Bourrillion Dec 22 '09 at 01:11
  • 1
    https://stackoverflow.com/a/30984049/1808829 could help you – Ayaz Alifov Apr 30 '18 at 17:16
  • I don't understand why `LinkedHashMap` doesn't implement `Deque`. It might as well. – forresthopkinsa Jan 19 '22 at 19:50
  • 2
    To follow up on @KevinBourrillion's comment from 13 years ago, this is finally being added in Java 21 by the [sequenced collections](https://openjdk.org/jeps/431) feature ([JDK-8280836](https://bugs.openjdk.org/browse/JDK-8280836)). I've added this as an answer (https://stackoverflow.com/a/76461679/1108305). – M. Justin Jun 13 '23 at 04:28

17 Answers17

195

The semantics of LinkedHashMap are still those of a Map, rather than that of a LinkedList. It retains insertion order, yes, but that's an implementation detail, rather than an aspect of its interface.

The quickest way to get the "first" entry is still entrySet().iterator().next(). Getting the "last" entry is possible, but will entail iterating over the whole entry set by calling .next() until you reach the last. while (iterator.hasNext()) { lastElement = iterator.next() }

edit: However, if you're willing to go beyond the JavaSE API, Apache Commons Collections has its own LinkedMap implementation, which has methods like firstKey and lastKey, which do what you're looking for. The interface is considerably richer.

Henrik Sachse
  • 51,228
  • 7
  • 46
  • 59
skaffman
  • 398,947
  • 96
  • 818
  • 769
  • I have to insert entry(key, value) as a first entry in my linked map; OR something like index based insertion. is that possible with Apache collections? – Kanagavelu Sugumar Jun 20 '14 at 06:40
  • 2
    The "LinkedMap," "firstKey," and "lastKey" links are stale, fyi. – Josh Sep 22 '14 at 04:10
  • Appears you actually maybe can even use the Commons LinkedMap to traverse it in reverse order, by getting lastKey then using previousKey after that to step backward...nice. – rogerdpack Dec 09 '15 at 16:28
  • Appears you actually maybe can even use the Commons LinkedMap to traverse it in reverse order, by getting lastKey then using previousKey after that to step backward...nice. You could then even write your own Iterable if you wanted to use it in foreach loops ex: http://stackoverflow.com/a/1098153/32453 – rogerdpack Dec 09 '15 at 16:59
  • results in infinite loop, because lastentry .next returns the first entry – Jason Jan 29 '16 at 09:01
  • how can i convert from LinkedHashMap to apache LinkedMap? – Shai M. Dec 15 '16 at 06:59
  • 2
    @skaffman , could you please help : what is `mylinkedmap.entrySet().iterator().next()` time complexity? Is it O(1) ? – tkrishtop Feb 21 '19 at 20:36
  • 1
    @tkrishtop late answer but I believe it should be O(1) as the LinkedHashMap has a pointer to the head of the queue. – Ricola Dec 06 '20 at 16:47
29

Can you try doing something like (to get the last entry):

linkedHashMap.entrySet().toArray()[linkedHashMap.size() -1];
frankelot
  • 13,666
  • 16
  • 54
  • 89
  • 8
    It is still faster to iterate and keep the last item. `T last = null ; for( T item : linkedHashMap.values() ) last = item;` Or something like that. It is O(N) in time but O(1) in memory. – Florian F Jul 27 '17 at 12:29
  • @FlorianF So it depends how big is your list. The array solution would be faster and wouldn't compromise memory if the collection is not that big, otherwise it's better taking longer to iterate thru it... I wonder if there's a bit of both as a solution, especially since Java 8. – skinny_jones May 16 '18 at 19:09
  • 2
    @skinny_jones: Why would the array solution be faster? It still involves iterating over the whole map, it's just that now the iteration is inside a JDK method instead of explicit. – ruakh Apr 01 '19 at 21:07
26

I know that I came too late but I would like to offer some alternatives, not something extraordinary but some cases that none mentioned here. In case that someone doesn't care so much for efficiency but he wants something with more simplicity(perhaps find the last entry value with one line of code), all this will get quite simplified with the arrival of Java 8 . I provide some useful scenarios.

For the sake of the completeness, I compare these alternatives with the solution of arrays that already mentioned in this post by others users. I sum up all the cases and i think they would be useful(when performance does matter or no) especially for new developers, always depends on the matter of each problem

Possible Alternatives

Usage of Array Method

I took it from the previous answer to to make the follow comparisons. This solution belongs @feresr.

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

Usage of ArrayList Method

Similar to the first solution with a little bit different performance

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

Reduce Method

This method will reduce the set of elements until getting the last element of stream. In addition, it will return only deterministic results

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

SkipFunction Method

This method will get the last element of the stream by simply skipping all the elements before it

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

Iterable Alternative

Iterables.getLast from Google Guava. It has some optimization for Lists and SortedSets too

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

Here is the full source code

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

Here is the output with performance of each method

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
  • 2,851
  • 4
  • 31
  • 43
13

LinkedHashMap current implementation (Java 8) keeps track of its tail. If performance is a concern and/or the map is large in size, you could access that field via reflection.

Because the implementation may change it is probably a good idea to have a fallback strategy too. You may want to log something if an exception is thrown so you know that the implementation has changed.

It could look like:

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);
}
assylias
  • 321,522
  • 82
  • 660
  • 783
6

One more way to get first and last entry of a LinkedHashMap is to use toArray() method of Set interface.

But I think iterating over the entries in the entry set and getting the first and last entry is a better approach.

The usage of array methods leads to warning of the form " ...needs unchecked conversion to conform to ..." which cannot be fixed [but can be only be suppressed by using the annotation @SuppressWarnings("unchecked")].

Here is a small example to demonstrate the usage of toArray() method:

    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
Wolfson
  • 1,187
  • 17
  • 22
sateesh
  • 27,947
  • 7
  • 36
  • 45
  • 4
    the toArray method itself will iterate over the hashmap again :) So its rather more inefficient because of the few extra cycles wasted in creating array and the space allocated for the array. – Durin May 29 '12 at 15:13
5

It's a bit dirty, but you can override the removeEldestEntry method of LinkedHashMap, which it might suit you to do as a private anonymous member:

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

So you will always be able to get the first entry at your eldest member. It will be updated every time you perform a put.

It should also be easy to override put and set youngest ...

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

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

It all breaks down when you start removing entries though; haven't figured out a way to kludge that.

It's very annoying that you can't otherwise get access to head or tail in a sensible way ...

robert
  • 4,612
  • 2
  • 29
  • 39
  • Good try, but the eldest entry is updated when map.get is invoked. So eldestEntry will not be updated in those cases, unless put is called after that. – vishr Nov 23 '16 at 23:43
  • @vishr eldest entry is updated when put is invoked. (get and put for access order LinkedHashMap) – Shiji.J Nov 15 '19 at 15:03
4

Perhaps something like this :

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

Using Java8 stream this can be done very easily:

LinkedHashMap<String, Integer> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("A", 1);
linkedHashMap.put("B", 2);
linkedHashMap.put("C", 3);
linkedHashMap.put("D", 4);

//First entry
Map.Entry<String, Integer> firstEntry = linkedHashMap.entrySet().stream().findFirst().get();

//Last entry
Map.Entry<String, Integer> lastEntry = linkedHashMap.entrySet().stream().skip(linkedHashMap.size() - 1).findFirst().get();
2

Suggestion:

map.remove(map.keySet().iterator().next());
Tadeu Jr.
  • 361
  • 3
  • 6
  • it gives the first inserted key in the map. Finding the first key will be in O(1). The problem here is finding a way in O(1) for finding the last inserted key. – E A Apr 24 '20 at 21:01
2

For first element use entrySet().iterator().next() and stop iterating after 1 iteration. For last one the easiest way is to preserve the key in a variable whenever you do a map.put.

Arnab
  • 41
  • 4
2

The first and last entry can be easily accessed using the firstEntry() and lastEntry() methods which are being added in Java 21 as part of the sequenced collections enhancement:

LinkedHashMap<Integer, String> linkedHashMap = // ...

Entry<Integer, String> firstEntry = linkedHashMap.firstEntry();
Entry<Integer, String> lastValue = linkedHashMap.lastEntry();

Integer firstKey = firstEntry.getKey();
Integer lastKey = lastEntry.getKey();
String firstValue = firstEntry.getValue();
String lastValue = lastEntry.getValue();
M. Justin
  • 14,487
  • 7
  • 91
  • 130
1

Though linkedHashMap doesn't provide any method to get first, last or any specific object.

But its pretty trivial to get :

Map<Integer,String> orderMap = new LinkedHashMap<Integer,String>();  
Set<Integer> al =   orderMap.keySet();

now using iterator on al object ; you can get any object.

Wolfson
  • 1,187
  • 17
  • 22
rai.skumar
  • 10,309
  • 6
  • 39
  • 55
1
        import java.util.Arrays;
        import java.util.LinkedHashMap;
        import java.util.List;
        import java.util.Map;

        public class Scratch {
           public static void main(String[] args) {

              // Plain java version

              Map<String, List<Integer>> linked = new LinkedHashMap<>();
              linked.put("a", Arrays.asList(1, 2, 3));
              linked.put("aa", Arrays.asList(1, 2, 3, 4));
              linked.put("b", Arrays.asList(1, 2, 3, 4, 5));
              linked.put("bb", Arrays.asList(1, 2, 3, 4, 5, 6));

              System.out.println("linked = " + linked);

              String firstKey = getFirstKey(linked);
              System.out.println("firstKey = " + firstKey);
              List<Integer> firstEntry = linked.get(firstKey);
              System.out.println("firstEntry = " + firstEntry);

              String lastKey = getLastKey(linked);
              System.out.println("lastKey = " + lastKey);
              List<Integer> lastEntry = linked.get(lastKey);
              System.out.println("lastEntry = " + lastEntry);



           }

           private static String getLastKey(Map<String, List<Integer>> linked) {
              int index = 0;
              for (String key : linked.keySet()) {
             index++;
             if (index == linked.size()) {
                return key;
             }
              }
              return null;
           }

           private static String getFirstKey(Map<String, List<Integer>> linked) {
              for (String key : linked.keySet()) {
             return key;
              }
              return null;
           }
        }
Atlilo
  • 11
  • 2
  • Can you maybe add explanation to your answer, to explain what do you have do different/ what do you add or remove. – zerbene Nov 10 '20 at 09:49
0

I would recommend using ConcurrentSkipListMap which has firstKey() and lastKey() methods

Doua Beri
  • 10,612
  • 18
  • 89
  • 138
  • 1
    ConcurrentSkipListMap requires a comparator (or natural comparator), so you need to do extra work to save the order in which entries are put. – AlikElzin-kilaka Sep 23 '12 at 12:17
  • HashMap and specifically LinkedHashMap provides access in average of O(1) - contrary to a SkipList, which provides access in average of O(logn). – AlikElzin-kilaka Sep 23 '12 at 13:02
  • 1
    "The map is sorted according to the natural ordering of its keys" –  Apr 27 '17 at 13:25
0

Yea I came across the same problem, but luckily I only need the first element... - This is what I did for it.

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

If you need the last element as well - I'd look into how to reverse the order of your map - store it in a temp variable, access the first element in the reversed map(therefore it would be your last element), kill the temp variable.

Here's some good answers on how to reverse order a hashmap:

How to iterate hashmap in reverse order in Java

If you use help from the above link, please give them up-votes :) Hope this can help someone.

Community
  • 1
  • 1
shecodesthings
  • 1,218
  • 2
  • 15
  • 33
0

right, you have to manually enumerate keyset till the end of the linkedlist, then retrieve the entry by key and return this entry.

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