Java LinkedHashMapは最初または最後のエントリを取得します


138

私が使用しているLinkedHashMap、それはキーがマップに入力された順序が重要であるため。

しかし、今私は最初の場所(最初に入力されたエントリ)または最後のキーの値を取得したいと思います。

以下のような方法があるはずですfirst()し、last()そのようか何かを?

最初のキーエントリを取得するだけのイテレータが必要ですか?それが私が使用した理由LinkedHashMapです!

ありがとう!


3
状況は確かに残念です。必要な機能を提供する(優先度の低い)機能リクエストを次に示し
Kevin Bourrillion

回答:


158

のセマンティクスは、のセマンティクスで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」のリンクは古くなっています。
Josh、

実際には、おそらくCommons LinkedMapを使用してそれを逆の順序でトラバースすることもできます。lastKeyを取得し、その後でpreviousKeyを使用して後退します...素敵です。
rogerdpack

実際には、おそらくCommons LinkedMapを使用してそれを逆の順序でトラバースすることもできます。lastKeyを取得し、その後でpreviousKeyを使用して後退します...素敵です。foreachループで使用する場合は、独自のIterableを作成することもできます。例:stackoverflow.com/a/1098153/32453
rogerdpack

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)です。
フロリアンF

@FlorianFでは、リストの大きさによって異なります。コレクションは解決策として、両方のビットがありますならば、それ以外の場合は、それを通して反復するために時間がかかっ方が良いでしょう...私は特にJavaの8以来、大きな疑問ということではない場合、配列のソリューションは、より高速かつ妥協のメモリではないだろうだろう
skinny_jones

1
@skinny_jones:アレイソリューションの方が速いのはなぜですか?それでもマップ全体を反復処理する必要があります。反復が明示的ではなくJDKメソッド内にあるだけです。
ruakh

17

私は遅すぎたことを知っていますが、並外れたものではなく、ここで言及されていないいくつかのケースをいくつか提供したいと思います。誰かが効率をあまり気にしないが、もっと単純なものを望んでいる場合(おそらく、1行のコードで最後のエントリの値を見つける)、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();
    }

反復可能な代替

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
ああ、「汚い」答え:)
rogerdpack '09 / 12/15

6

LinkedHashMapの最初と最後のエントリを取得するもう1つの方法は、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メソッド自体は、もう一度ハッシュマップを反復処理します:)配列の作成に費やされた余分なサイクルと配列に割り当てられたスペースのため、かなり非効率的です。
Durin、2012年

5

少し汚いですがremoveEldestEntry、LinkedHashMap のメソッドをオーバーライドできます。これは、プライベートの匿名メンバーとして行うのに適している場合があります。

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 2016年

@vishrの最も古いエントリは、putが呼び出されると更新されます。(アクセス順序LinkedHashMapの取得と配置)
Shiji.J '15

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

私が使用することをお勧めしますConcurrentSkipListMapの持っているfirstKey()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は、最初、最後、または特定のオブジェクトを取得するメソッドを提供していません。

しかし、取得するのはかなり簡単です:

  • map orderMap = new LinkedHashMap();
    セットal = orderMap.keySet();

現在、alオブジェクトでイテレータを使用しています。任意のオブジェクトを取得できます。


0

ええ、私は同じ問題に遭遇しましたが、幸いにも最初の要素だけが必要です...-これは私がそれのためにしたことです。

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

最後の要素も必要な場合-私はマップの順序を逆にする方法を調べます-それを一時変数に保存し、反転したマップの最初の要素にアクセスします(したがって、それが最後の要素になります)、一時変数。

ハッシュマップを逆順で並べる方法について、いくつかの良い答えがあります:

Javaでハッシュマップを逆の順序で反復する方法

上記のリンクからのヘルプを使用する場合は、賛成票を投じてください:)これが誰かの役に立つことを願っています。


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;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.