Java LinkedHashMap mendapatkan entri pertama atau terakhir

146

Saya telah menggunakan LinkedHashMapkarena itu penting urutan kunci yang dimasukkan di peta.

Tapi sekarang saya ingin mendapatkan nilai kunci di tempat pertama (entri yang pertama masuk) atau yang terakhir.

Haruskah ada metode seperti first()dan last()atau sesuatu seperti itu?

Apakah saya perlu memiliki iterator untuk mendapatkan entri kunci pertama? Itulah mengapa saya menggunakan LinkedHashMap!

Terima kasih!

maiky
sumber
3
Situasinya memang sangat disayangkan. Berikut adalah permintaan fitur (prioritas rendah) yang akan menyediakan apa yang Anda butuhkan: bugs.sun.com/view_bug.do?bug_id=6266354
Kevin Bourrillion
stackoverflow.com/a/30984049/1808829 dapat membantu Anda
Ayaz Alifov

Jawaban:

167

Semantik LinkedHashMapmasih berupa Peta, bukan dari a LinkedList. Itu mempertahankan urutan penyisipan, ya, tapi itu detail implementasi, bukan aspek antarmukanya.

Cara tercepat untuk mendapatkan entri "pertama" adalah diam entrySet().iterator().next(). Mendapatkan entri "terakhir" dimungkinkan, tetapi akan memerlukan pengulangan atas seluruh entri yang ditetapkan dengan menelepon .next()hingga Anda mencapai yang terakhir. while (iterator.hasNext()) { lastElement = iterator.next() }

sunting : Namun, jika Anda ingin melampaui JavaSE API, Apache Commons Collections memiliki LinkedMapimplementasinya sendiri , yang memiliki metode seperti firstKeydan lastKey, yang melakukan apa yang Anda cari. Antarmukanya jauh lebih kaya.

skaffman
sumber
Saya harus memasukkan entri (kunci, nilai) sebagai entri pertama di peta tertaut saya; ATAU sesuatu seperti penyisipan berbasis indeks. apakah itu mungkin dengan koleksi Apache?
Kanagavelu Sugumar
2
Link "LinkedMap", "firstKey", dan "lastKey" sudah basi, fyi.
Josh
Tampaknya Anda sebenarnya mungkin bahkan dapat menggunakan Commons LinkedMap untuk melewatinya dalam urutan terbalik, dengan mendapatkan lastKey kemudian menggunakan beforeKey setelah itu untuk melangkah mundur ... bagus.
rogerdpack
Tampaknya Anda sebenarnya mungkin bahkan dapat menggunakan Commons LinkedMap untuk melewatinya dalam urutan terbalik, dengan mendapatkan lastKey kemudian menggunakan beforeKey setelah itu untuk melangkah mundur ... bagus. Anda bahkan dapat menulis Iterable Anda sendiri jika Anda ingin menggunakannya di foreach loop ex: stackoverflow.com/a/1098153/32453
rogerdpack
1
@skaffman, dapatkah Anda membantu: apa mylinkedmap.entrySet().iterator().next()itu kompleksitas waktu? Apakah itu O (1)?
tkrishtop
27

Bisakah Anda mencoba melakukan sesuatu seperti (untuk mendapatkan entri terakhir):

linkedHashMap.entrySet().toArray()[linkedHashMap.size() -1];
FRR
sumber
5
Masih lebih cepat untuk mengulang dan menyimpan item terakhir. T last = null ; for( T item : linkedHashMap.values() ) last = item; Atau semacam itu. Ini adalah O (N) dalam waktu tetapi O (1) dalam memori.
Florian F
@FlorianF Jadi itu tergantung seberapa besar daftar Anda. Solusi array akan lebih cepat dan tidak akan mengganggu memori jika koleksinya tidak terlalu besar, jika tidak, lebih baik membutuhkan waktu lebih lama untuk mengulanginya ... Saya ingin tahu apakah ada sedikit dari keduanya sebagai solusi, terutama sejak Java 8.
skinny_jones
2
@skinny_jones: Mengapa solusi array lebih cepat? Ini masih melibatkan iterasi ke seluruh peta, hanya saja sekarang iterasinya ada di dalam metode JDK, bukan eksplisit.
ruakh
20

Saya tahu bahwa saya datang terlambat tetapi saya ingin menawarkan beberapa alternatif, bukan sesuatu yang luar biasa tetapi beberapa kasus yang tidak disebutkan di sini. Jika seseorang tidak terlalu peduli dengan efisiensi tetapi dia menginginkan sesuatu yang lebih sederhana (mungkin menemukan nilai entri terakhir dengan satu baris kode), semua ini akan menjadi cukup disederhanakan dengan hadirnya Java 8 . Saya memberikan beberapa skenario yang berguna.

Demi kelengkapan, saya membandingkan alternatif ini dengan solusi array yang sudah disebutkan dalam posting ini oleh pengguna lain. Saya merangkum semua kasus dan saya pikir itu akan berguna (ketika kinerja itu penting atau tidak) terutama untuk pengembang baru, selalu tergantung pada masalah setiap masalah

Alternatif yang Mungkin

Penggunaan Metode Array

Saya mengambilnya dari jawaban sebelumnya untuk membuat perbandingan berikut. Solusi ini milik @feresr.

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

Penggunaan Metode ArrayList

Mirip dengan solusi pertama dengan kinerja yang sedikit berbeda

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

Kurangi Metode

Metode ini akan mengurangi himpunan elemen hingga mendapatkan elemen aliran terakhir. Selain itu, ini hanya akan mengembalikan hasil deterministik

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

Metode SkipFunction

Metode ini akan mendapatkan elemen terakhir dari aliran hanya dengan melewatkan semua elemen sebelumnya

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

Alternatif Iterable

Iterables.getLast dari Google Guava. Ini memiliki beberapa pengoptimalan untuk Daftar dan SortedSets juga

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

Ini kode sumber lengkapnya

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

Berikut adalah output dengan performa masing-masing metode

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
sumber
11

LinkedHashMapimplementasi saat ini (Java 8) melacak ekornya. Jika kinerja menjadi perhatian dan / atau peta berukuran besar, Anda dapat mengakses bidang itu melalui refleksi.

Karena penerapannya dapat berubah, mungkin ada baiknya untuk memiliki strategi fallback juga. Anda mungkin ingin mencatat sesuatu jika pengecualian dilempar sehingga Anda tahu bahwa implementasinya telah berubah.

Ini bisa terlihat seperti:

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
sumber
1
Saya pikir saya akan menambahkan ClassCastExceptionuntuk catchberjaga-jaga tailbukan Entrydalam subclass (atau implementasi di masa depan).
Paul Boddington
@PaulBoddington Saya telah menggantinya dengan semua - tidak direkomendasikan secara umum tetapi mungkin sesuai di sini.
assylias
7
ahh jawaban yang "kotor" :)
rogerdpack
6

Satu cara lagi untuk mendapatkan entri pertama dan terakhir dari LinkedHashMap adalah dengan menggunakan toArray()metode Set interface.

Tapi saya pikir mengulangi entri dalam set entri dan mendapatkan entri pertama dan terakhir adalah pendekatan yang lebih baik.

Penggunaan metode array mengarah ke peringatan bentuk "... membutuhkan konversi yang tidak dicentang agar sesuai dengan ..." yang tidak dapat diperbaiki [tetapi hanya dapat disembunyikan dengan menggunakan anotasi @SuppressWarnings("unchecked")].

Berikut adalah contoh kecil untuk mendemonstrasikan penggunaan toArray()metode:

    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
sateesh
sumber
4
metode toArray itu sendiri akan mengulangi lagi hashmap tersebut :) Jadi ini agak lebih tidak efisien karena beberapa siklus tambahan yang terbuang dalam pembuatan array dan ruang yang dialokasikan untuk array.
Durin
5

Ini agak kotor, tetapi Anda dapat mengganti removeEldestEntrymetode LinkedHashMap, yang mungkin cocok untuk Anda lakukan sebagai anggota anonim pribadi:

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

Jadi Anda akan selalu bisa mendapatkan entri pertama pada eldestanggota Anda . Ini akan diperbarui setiap kali Anda melakukan put.

Ini juga harus mudah untuk mengganti putdan mengatur youngest...

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

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

Semuanya rusak ketika Anda mulai menghapus entri; belum menemukan cara untuk menutupinya.

Sangat menjengkelkan bahwa Anda tidak bisa mendapatkan akses ke kepala atau ekor dengan cara yang masuk akal ...

robert
sumber
Usaha yang bagus, tetapi entri tertua diperbarui saat map.get dipanggil. Jadi eldestEntry tidak akan diperbarui dalam kasus tersebut, kecuali put dipanggil setelah itu.
vishr
Entri tertua @vishr diperbarui ketika put dipanggil. (dapatkan dan pasang untuk pesanan akses LinkedHashMap)
Shiji.J
3

Mungkin sesuatu seperti ini:

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;
}
pengguna2127649
sumber
2

Saran:

map.remove(map.keySet().iterator().next());
Tadeu Jr.
sumber
ini memberikan kunci yang disisipkan pertama di peta. Menemukan kunci pertama akan berada di O (1). Masalahnya di sini adalah menemukan cara di O (1) untuk menemukan kunci yang terakhir dimasukkan.
Emad Aghayi
1

Saya akan merekomendasikan menggunakan ConcurrentSkipListMap yang memiliki firstKey()dan lastKey()metode

Doua Beri
sumber
1
ConcurrentSkipListMap membutuhkan pembanding (atau pembanding alami), jadi Anda perlu melakukan pekerjaan ekstra untuk menyimpan urutan entri dimasukkan.
AlikElzin-kilaka
HashMap dan secara khusus LinkedHashMap menyediakan akses rata-rata O (1) - bertentangan dengan SkipList, yang menyediakan akses rata-rata O (logn).
AlikElzin-kilaka
1
"Peta diurutkan menurut urutan alami kuncinya"
1

Untuk penggunaan elemen pertama entrySet().iterator().next()dan hentikan iterasi setelah 1 iterasi. Untuk yang terakhir, cara termudah adalah dengan mempertahankan kunci dalam variabel setiap kali Anda melakukan map.put.

Arnab
sumber
0

Ya, saya menemukan masalah yang sama, tapi untungnya saya hanya membutuhkan elemen pertama ... - Inilah yang saya lakukan untuk itu.

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

Jika Anda membutuhkan elemen terakhir juga - saya akan melihat cara membalik urutan peta Anda - menyimpannya dalam variabel temp, akses elemen pertama di peta terbalik (oleh karena itu itu akan menjadi elemen terakhir Anda), bunuh variabel temp.

Berikut beberapa jawaban bagus tentang cara membalik urutan peta hash:

Cara mengiterasi hashmap dalam urutan terbalik di Java

Jika Anda menggunakan bantuan dari tautan di atas, tolong beri mereka suara :) Semoga ini dapat membantu seseorang.

ryvianstyron.dll
sumber
0

benar, Anda harus secara manual menghitung kumpulan kunci hingga akhir daftar tertaut, kemudian mengambil entri dengan kunci dan mengembalikan entri ini.

tidak diketahui
sumber
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;
}
Владислав Шестернин
sumber
0

Meskipun linkedHashMap tidak menyediakan metode apa pun untuk mendapatkan objek pertama, terakhir, atau tertentu.

Tapi cukup sepele untuk mendapatkannya:

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

sekarang menggunakan iterator pada alobjek; Anda bisa mendapatkan benda apapun.

rai.skumar
sumber