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

Posted

技术标签:

【中文标题】Java LinkedHashMap 获取第一个或最后一个条目【英文标题】:Java LinkedHashMap get first or last entry 【发布时间】:2010-12-28 12:46:04 【问题描述】:

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

但现在我想在第一个(第一个输入的条目)或最后一个获取 key 的值。

应该有first()last()之类的方法吗?

我是否需要一个迭代器才能获取第一个键条目?这就是我使用LinkedHashMap的原因!

谢谢!

【问题讨论】:

情况确实很不幸。以下是(低优先级)功能请求,可满足您的需求:bugs.sun.com/view_bug.do?bug_id=6266354 ***.com/a/30984049/1808829 可以帮助你 我不明白为什么LinkedHashMap 没有实现Deque。也可以。 【参考方案1】:

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

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

编辑:但是,如果您愿意超越 JavaSE API,Apache Commons Collections 有自己的 LinkedMap 实现,它有像 firstKeylastKey 这样的方法,其中做你正在寻找的东西。界面要丰富得多。

【讨论】:

我必须将 entry(key, value) 作为我的链接地图中的第一个条目插入;或者类似基于索引的插入。 Apache 集合可以做到这一点吗? “LinkedMap”、“firstKey”和“lastKey”链接已过时,仅供参考。 看来您实际上甚至可以使用 Commons LinkedMap 以相反的顺序遍历它,通过获取 lastKey 然后使用 previousKey 后退一步......很好。 @skaffman ,请您帮忙:mylinkedmap.entrySet().iterator().next() 时间复杂度是多少?是 O(1) 吗? @tkrishtop 迟到的答案,但我相信它应该是 O(1),因为 LinkedHashMap 有一个指向队列头部的指针。【参考方案2】:

获取 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

【讨论】:

toArray 方法本身将再次遍历 hashmap :) 所以它的效率相当低,因为在创建数组和为数组分配的空间中浪费了一些额外的周期。【参考方案3】:

我建议使用ConcurrentSkipListMap,它具有firstKey()lastKey() 方法

【讨论】:

ConcurrentSkipListMap 需要一个比较器(或自然比较器),因此您需要做额外的工作来保存条目的放置顺序。 HashMap 尤其是 LinkedHashMap 提供平均 O(1) 的访问 - 与 SkipList 相反,它提供平均 O(logn) 的访问。 "地图按其键的自然顺序排序"【参考方案4】:

虽然linkedHashMap没有提供任何获取first、last或任何特定对象的方法。

但它很容易获得:

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

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

【讨论】:

【参考方案5】:

也许是这样的:

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;

【讨论】:

【参考方案6】:

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

private String getDefaultPlayerType()

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

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

这里有一些关于如何反转哈希图的好答案:

How to iterate hashmap in reverse order in Java

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

【讨论】:

【参考方案7】:

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

【讨论】:

我想我会在catch 中添加ClassCastException,以防tail 不是子类(或未来实现)中的Entry @PaulBoddington 我已经把它换成了一个包罗万象的东西——一般不推荐,但在这里可能合适。 啊“肮脏”的答案:)【参考方案8】:

这有点脏,但你可以重写 LinkedHashMap 的removeEldestEntry 方法,它可能适合你作为私有匿名成员:

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 时会更新。所以 eldestEntry 在这些情况下不会被更新,除非之后调用 put。 @vishr eldest 条目在调用 put 时更新。 (获取并放置访问顺序LinkedHashMap)【参考方案9】:

建议:

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

【讨论】:

它给出了地图中第一个插入的键。找到第一个键将在 O(1) 中。这里的问题是在 O(1) 中找到一种方法来找到最后插入的密钥。【参考方案10】:

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

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

【讨论】:

迭代并保留最后一项仍然更快。 T last = null ; for( T item : linkedHashMap.values() ) last = item; 或类似的东西。时间是 O(N),但内存是 O(1)。 @FlorianF 所以这取决于你的名单有多大。如果集合不是那么大,数组解决方案会更快并且不会影响内存,否则最好花更长的时间来迭代它......我想知道是否有两者兼而有之的解决方案,尤其是从 Java 8 开始。 @skinny_jones:为什么阵列解决方案会更快?它仍然涉及对整个映射的迭代,只是现在迭代是在 JDK 方法内部而不是显式的。【参考方案11】:

我知道我来得太晚了,但我想提供一些替代方案,不是什么特别的东西,而是一些这里没有提到的案例。如果有人不太关心效率,但他想要更简单的东西(也许用一行代码找到最后一个入口值),所有这一切都会随着 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();
    

SkipFunction 方法

此方法将通过简单地跳过它之前的所有元素来获取流的最后一个元素

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

可迭代替代

Iterables.getLast 来自 Google Guava。它有一些优化 Lists 和 SortedSets 也是如此

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

【讨论】:

【参考方案12】:

对,你必须手动枚举keyset直到链表的末尾,然后通过key检索条目并返回这个条目。

【讨论】:

【参考方案13】:
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;

【讨论】:

【参考方案14】:

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

【讨论】:

【参考方案15】:
        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;
           
        

【讨论】:

你能否在你的答案中添加解释,解释你有什么不同/你添加或删除了什么。【参考方案16】:

使用Java8 stream 可以很容易地做到这一点:

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

【讨论】:

以上是关于Java LinkedHashMap 获取第一个或最后一个条目的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Kotlin Android 中的 LinkedHashMap 手动获取第一和第二位置的对象?

JAVA集合LinkedHashMap及其源码分析

HashMap的ArrayList或LinkedHashMap的按索引获取项目

Java中HashMap和LinkedHashMap以及TreeMap的区别

在 Java 中收缩 LinkedHashMap

java LinkedHashMap,TreeMap,HashMap