Java LinkedHashMap ilk veya son girişi al


139

Kullandım LinkedHashMapçünkü haritadaki tuşların girilme sırası önemlidir.

Ama şimdi anahtarın değerini ilk etapta (ilk girilen giriş) veya son olarak almak istiyorum.

Bir benzeri yöntem olmalı first()ve last()bunun gibi ya da bir şey?

Sadece ilk tuş girişini almak için bir yineleyiciye ihtiyacım var mı? Bu yüzden kullandım LinkedHashMap!

Teşekkürler!


3
Durum gerçekten talihsiz. İhtiyacınız olanı sağlayacak (düşük öncelikli) özellik isteği: bugs.sun.com/view_bug.do?bug_id=6266354
Kevin Bourrillion

Yanıtlar:


159

Anlambilimi LinkedHashMap, a LinkedList. Ekleme sırasını korur, evet, ancak bu, arayüzünün bir yönü yerine bir uygulama ayrıntısıdır.

"İlk" girişi almanın en hızlı yolu hala entrySet().iterator().next(). "Son" girişi elde etmek mümkündür, ancak son girişe .next()ulaşana kadar arayarak tüm giriş setini yinelemeyi gerektirir . while (iterator.hasNext()) { lastElement = iterator.next() }

edit : Ancak, JavaSE API ötesine geçmeye hazırsanız, Apache Commons Koleksiyonlar kendi LinkedMapuygulaması vardır, hangi gibi yöntemler vardır firstKeyve lastKeyaradığınızı yapar. Arayüz oldukça zengin.


Bağlantılı eşlememdeki ilk girdi olarak girdi (anahtar, değer) eklemek zorundayım; VEYA dizin tabanlı ekleme gibi bir şey. koleksiyonları ile bu mümkün mü?
Kanagavelu Sugumar

2
"LinkedMap", "firstKey" ve "lastKey" bağlantıları eski, fyi.
Josh

Görünüşe göre aslında Commons LinkedMap'i ters sırayla hareket ettirmek için bile kullanabilirsiniz, lastKey sonra geri adım atmak için previousKey kullanarak ... güzel.
rogerdpack

Görünüşe göre aslında Commons LinkedMap'i ters sırayla hareket ettirmek için bile kullanabilirsiniz, lastKey sonra geri adım atmak için previousKey kullanarak ... güzel. Daha sonra foreach döngülerinde kullanmak isterseniz kendi
Yinelenebilir öğenizi

1
@skaffman, lütfen yardım edebilir misiniz: mylinkedmap.entrySet().iterator().next()zaman karmaşıklığı nedir? O (1) mi?
tkrishtop

25

(Son girişi almak için) gibi bir şey yapmayı deneyebilir misiniz?

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

5
Son öğeyi yinelemek ve saklamak hala daha hızlı. T last = null ; for( T item : linkedHashMap.values() ) last = item; Ya da böyle bir şey. Zaman içinde O (N), ancak hafızada O (1) 'dir.
Florian F

@FlorianF Listenizin ne kadar büyük olduğuna bağlıdır. Dizi çözüm daha hızlı olacağını ve olmaz taviz bellek koleksiyonu aksi takdirde daha iyi bir çözüm olarak ikisinden de biraz var mı, özellikle Java 8. beri merak ediyorum ... artık yinelemenizi aracılığıyla o götürüyor, o büyük değilse
skinny_jones

1
@skinny_jones: Dizi çözümü neden daha hızlı olsun? Hala tüm harita üzerinde yineleme içerir, sadece şimdi yineleme açık yerine bir JDK yönteminin içindedir.
ruakh

18

Çok geç geldiğimi biliyorum ama bazı alternatifler sunmak istiyorum, olağanüstü bir şey değil, burada belirtilmeyen bazı durumlar. Birisinin verimlilik için çok fazla umursamaması, ancak daha basit bir şey istemesi durumunda (belki de bir satır kod ile son giriş değerini bulun), tüm bunlar Java 8'in gelişiyle oldukça basitleşecektir . Bazı faydalı senaryolar sağlarım.

Tamlık uğruna, bu alternatifleri, bu yayında daha önce belirtilen diğer kullanıcıların dizileriyle karşılaştırıyorum. Tüm vakaları özetliyorum ve özellikle yeni geliştiriciler için (performans önemli veya hayır olduğunda) yararlı olacağını düşünüyorum, her zaman her sorunun konusuna bağlıdır

Olası Alternatifler

Dizi Yönteminin Kullanımı

Takip eden karşılaştırmaları yapmak için önceki cevaptan aldım. Bu çözüm @feresr'e aittir.

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

ArrayList Yönteminin Kullanımı

Biraz farklı performansa sahip ilk çözüme benzer

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

Azaltma Yöntemi

Bu yöntem, akışın son öğesini alana kadar öğe kümesini azaltacaktır. Ayrıca, yalnızca deterministik sonuçlar getirecektir.

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

SkipFunction Yöntemi

Bu yöntem, akıştan önceki tüm öğeleri yalnızca atlamadan önce alır

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

Tekrarlanabilir Alternatif

Google Guava'dan Iterables.getLast. Listeler ve SortedSets için de bazı optimizasyonlar var

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

İşte tam kaynak kodu

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

İşte her yöntemin performansı ile çıktı

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

LinkedHashMapmevcut uygulama (Java 8) kuyruğunu takip eder. Performans bir endişe kaynağıysa ve / veya haritanın boyutu büyükse bu alana yansıma ile erişebilirsiniz.

Uygulama değişebileceğinden, bir geri dönüş stratejisine sahip olmak muhtemelen iyi bir fikirdir. Bir istisna atılırsa, uygulamanın değiştiğini bilmek için bir şey günlüğe kaydetmek isteyebilirsiniz.

Şöyle görünebilir:

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
Ben eklemek düşünüyorum ClassCastExceptioniçin catchdurumunda adil taildeğil ise Entrybir alt sınıf (ya da gelecekte uygulanması) içinde.
Paul Boddington

@PaulBoddington Bir catch all ile değiştirdim - genel olarak önerilmez ama muhtemelen burada uygun.
assylias

7
ahh "kirli" cevap :)
rogerdpack

6

LinkedHashMap'in ilk ve son girişini almanın bir yolu da Set arabiriminin "toArray" yöntemini kullanmaktır.

Ancak giriş kümesindeki girdileri yinelemek ve ilk ve son girişi almak daha iyi bir yaklaşımdır.

Dizi yöntemlerinin kullanılması, "... 'e uyum sağlamak için denetlenmeyen dönüştürmeye ihtiyaç duyuyor ..." biçiminin uyarılmasına yol açar; bu, düzeltilemez [ancak yalnızca @SuppressWarnings ("işaretlenmemiş") notu kullanılarak bastırılabilir.

"ToArray" yönteminin kullanımını göstermek için küçük bir örnek:

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 yönteminin kendisi tekrar hashmap üzerinde yinelenecektir :) Yani dizi oluşturma ve dizi için ayrılan alan israf birkaç boşa döngü nedeniyle oldukça daha verimsiz.
Durin

5

Biraz kirli, ancak removeEldestEntryözel anonim bir üye olarak yapmanıza uygun olabilecek LinkedHashMap yöntemini geçersiz kılabilirsiniz :

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

Böylece eldestüyeye ilk girişi her zaman alabilirsin . Her gerçekleştirdiğinizde güncellenecektir put.

Ayrıca geçersiz kılmak putve ayarlamak kolay olmalıdır youngest...

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

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

Yine de girişleri kaldırmaya başladığınızda bozuluyor; Bunu kırmanın bir yolunu bulamadım.

Aksi halde kafaya veya kuyruğa mantıklı bir şekilde erişemeyeceğiniz çok sinir bozucu ...


İyi deneme, ancak map.get çağrıldığında en eski giriş güncellenir. Bundan sonra putd çağrılmadığı sürece bu durumda eldestEntry güncellenmeyecektir.
16:43 vishr

@vishr putd çağrıldığında en büyük girdi güncellenir. (get ve erişim siparişi için LinkedHashMap alın)
Shiji.J

2

Belki de böyle bir şey:

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

Öneri:

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

haritaya ilk eklenen anahtarı verir. İlk anahtarı bulmak O (1) olacaktır. Buradaki sorun, son eklenen anahtarı bulmak için O (1) 'de bir yol bulmaktır.
Emad Aghayi

1

Hangi ve yöntemleri ConcurrentSkipListMap kullanmanızı tavsiye ederimfirstKey()lastKey()


1
ConcurrentSkipListMap bir karşılaştırıcı (veya doğal karşılaştırıcı) gerektirir, bu nedenle girişlerin koyulma sırasını kaydetmek için ekstra iş yapmanız gerekir.
AlikElzin-kilaka

HashMap ve özellikle LinkedHashMap, ortalama O (logn) erişimi sağlayan bir SkipList'in aksine ortalama O (1) erişim sağlar.
AlikElzin-kilaka

"Harita anahtarlarının doğal sıralamasına göre sıralanmıştır"

1

İlk eleman için entrySet().iterator().next()1 yinelemeden sonra tekrarlayın ve tekrarlamayı durdurun. Sonuncusu için en kolay yol, bir map.put yaptığınızda bir değişkende anahtarı korumaktır.


0

LinkedHashMap, ilk, son veya belirli bir nesneyi almak için herhangi bir yöntem sağlamasa da.

Ama elde etmek oldukça önemsiz:

  • Harita sırasıMap = new LinkedHashMap ();
    Set al = orderMap.keySet ();

şimdi tüm nesne üzerinde yineleyici kullanmak; herhangi bir nesneyi alabilirsiniz.


0

Evet aynı problemle karşılaştım, ama neyse ki sadece ilk elemente ihtiyacım var ... - Bunun için yaptım.

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

Son öğeye de ihtiyacınız varsa - haritanızın sırasını nasıl tersine çevireceğimi düşünürüm - geçici bir değişkende saklar, ters haritada ilk öğeye erişir (bu nedenle son öğeniz olur), geçici değişken.

İşte bir hashmap'ı nasıl tersine çevireceğinize dair bazı iyi cevaplar:

Java'da hashmap ters sırada nasıl yinelenir

Yukarıdaki bağlantıdan yardım kullanıyorsanız, lütfen onlara oy verin :) Umarım bu birine yardımcı olabilir.


0

sağda, tuş takımını bağlantılı listenin sonuna kadar manuel olarak numaralandırmanız, ardından girişi tuşuyla almanız ve bu girişi geri döndürmeniz gerekir.


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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.