Java LinkedHashMap获取第一个或最后一个条目


139

我使用过,LinkedHashMap因为在地图中输入键的顺序很重要。

但是现在我想获得键的值在第一个位置(第一个输入的条目)或最后一个。

是否应该有类似first()和之last()类的方法?

我是否需要一个迭代器才能获取第一个密钥项?那就是为什么我用过LinkedHashMap

谢谢!


3
情况确实是不幸的。以下是(低优先级)功能请求,它可以满足
Kevin Bourrillion 2009年

Answers:


159

的语义LinkedHashMap仍然是地图的语义,而不是的语义LinkedList。是的,它保留插入顺序,但这只是实现细节,而不是其接口的一个方面。

获得“第一”条目的最快方法仍然是entrySet().iterator().next()。获取“最后一个”条目是可能的,但是将需要通过调用.next()直到到达最后一个条目来遍历整个条目集。 while (iterator.hasNext()) { lastElement = iterator.next() }

编辑:但是,如果您愿意超越JavaSE API,则Apache Commons Collections拥有自己的LinkedMap实现,该实现具有诸如firstKey和的方法lastKey,它们可以满足您的需求。界面相当丰富。


我必须在链接地图中插入entry(key,value)作为第一个条目;或者类似基于索引的插入。Apache收集有可能吗?
Kanagavelu Sugumar 2014年

2
“ LinkedMap”,“ firstKey”和“ lastKey”链接过时。
2014年

看来您实际上甚至可以使用Commons LinkedMap以相反的顺序遍历它,方法是先获取lastKey,然后再使用previousKey向后退...好。
rogerdpack 2015年

看来您实际上甚至可以使用Commons LinkedMap以相反的顺序遍历它,方法是先获取lastKey,然后再使用previousKey来向后退一步。如果您想在foreach循环中使用它,甚至可以编写自己的Iterable,例如:stackoverflow.com/a/1098153/32453
rogerdpack 2015年

1
@skaffman,您能帮忙mylinkedmap.entrySet().iterator().next()吗?时间复杂度是多少?是O(1)吗?
tkrishtop

25

您可以尝试做类似的事情(获取最后一个条目)吗:

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

5
迭代并保留最后一项仍然更快。 T last = null ; for( T item : linkedHashMap.values() ) last = item; 或类似的东西。时间为O(N),但内存为O(1)。
Florian F

@FlorianF因此,这取决于您的列表有多大。该阵列的解决方案将是更快,不会损害内存如果集合本来就不大,否则最好还是需要更长的时间来遍历通... ...我不知道是否有一点既作为解决方案,尤其是因为Java的8
skinny_jones

1
@skinny_jones:为什么数组解决方案会更快?它仍然涉及遍历整个地图,只是现在迭代是在JDK方法内部而不是显式的。
ruakh

17

我知道来得太晚了,但我想提供一些替代方法,不是非凡的选择,而是一些在此未提及的情况。如果某人不太在乎效率,但是他想要更简单的东西(也许用一行代码找到最后一个入口值),那么随着Java 8的到来,所有这些都将变得非常简单 。我提供了一些有用的方案。

为了完整起见,我将这些替代方案与其他用户已经在本文中提到的数组解决方案进行了比较。我总结了所有情况,并且我认为它们将是有用的(无论性能是否重要),对于新开发人员而言,总是取决于每个问题的问题

可能的选择

数组方法的用法

我从上一个答案中选择了进行以下比较。此解决方案属于@feresr。

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

ArrayList方法的用法

与第一个解决方案相似,但性能略有不同

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

迭代选择

来自Google Guava的Iterables.getLast。它也对列表和排序集进行了一些优化

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

这是完整的源代码

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

这是每种方法的性能输出

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

LinkedHashMap当前的实现(Java 8)跟踪其尾巴。如果需要考虑性能和/或地图很大,则可以通过反射访问该字段。

由于实现可能会发生变化,因此最好有一个后备策略。如果引发了异常,则可能需要记录一些内容,以便您知道实现已更改。

它可能看起来像:

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
我想我会添加ClassCastExceptioncatch万一tail不是Entry在子类中(或未来的实现)。
Paul Boddington,2015年

@PaulBoddington我已经用全部替换它了-一般不建议使用,但可能在这里合适。
assylias 2015年

7

6

获取LinkedHashMap的第一个条目和最后一个条目的另一种方法是使用Set接口的“ toArray”方法。

但是我认为对条目集中的条目进行迭代并获得第一个和最后一个条目是更好的方法。

数组方法的使用会导致警告“ ...需要未经检查的转换以符合...”的形式,该警告无法固定[但只能通过使用注释@SuppressWarnings(“ unchecked”)来抑制]。

这是一个小示例,用于演示“ 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
toArray方法本身将再次遍历hashmap :)因此,由于创建数组浪费了一些额外的循环以及为数组分配的空间,其效率更低。
杜林2012年

5

它有点脏,但是您可以重写removeEldestEntryLinkedHashMap 的方法,它可能适合您作为私有匿名成员执行:

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

因此,您将始终能够在您的eldest会员处获得第一笔入场券。每次您执行时都会更新put

它也应该易于覆盖put和设置youngest...

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

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

但是,当您开始删除条目时,一切都会崩溃;还没有找到一种方法去解决这个问题。

非常烦人的是,您否则无法以明智的方式使用头部或尾巴...


很好的尝试,但是最老的条目在调用map.get时更新。因此,除非在此之后调用put,否则在这种情况下eldestEntry将不会更新。
vishr '16

@vishr最老的条目在调用put时更新。(获取和放置访问顺序LinkedHashMap)
Shiji.J

2

也许是这样的:

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

建议:

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

它给出了地图中的第一个插入键。查找第一个键将在O(1)中。这里的问题是在O(1)中找到一种找到最后插入的键的方法。
Emad Aghayi

1

我建议使用具有和方法的ConcurrentSkipListMapfirstKey()lastKey()


1
ConcurrentSkipListMap需要一个比较器(或自然比较器),因此您需要做一些额外的工作来保存条目的放置顺序。
AlikElzin-kilaka 2012年

HashMap(特别是LinkedHashMap)平均提供O(1)的访问权限-与SkipList相反,后者提供平均O(logn)的访问权限。
AlikElzin-kilaka 2012年

“根据其键的自然顺序对地图进行排序”

1

对于第一个元素,请使用entrySet().iterator().next()并在1次迭代后停止迭代。对于最后一个,最简单的方法是在执行map.put时将键保留在变量中。


0

尽管linkedHashMap没有提供任何获取第一个,最后一个或任何特定对象的方法。

但是获得它很简单:

  • 地图orderMap = new LinkedHashMap();
    设置al = orderMap.keySet();

现在在对象上使用迭代器; 你可以得到任何物体。


0

是的,我遇到了同样的问题,但幸运的是,我只需要第一个要素...-这就是我所做的。

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

如果您还需要最后一个元素-我将研究如何反转地图的顺序-将其存储在temp变量中,访问反转后的地图中的第一个元素(因此它将是您的最后一个元素),请杀死临时变量。

这是一些有关如何反向排列哈希表的好答案:

如何在Java中以相反的顺序迭代hashmap

如果您使用上述链接中的帮助,请给他们投票:)希望这可以对某人有所帮助。


0

正确,您必须手动枚举密钥集直到链表的末尾,然后按密钥检索条目并返回此条目。


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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.