Java LinkedHashMap отримати перший або останній запис


139

Я використовував, LinkedHashMapтому що важливо порядок введення ключів у карту.

Але тепер я хочу отримати значення ключа в першу чергу (перший введений запис) або останній.

Чи повинні бути метод , як first()і last()чи що - щось подібне?

Чи потрібно мені мати ітератор, щоб отримати перший ключ? Ось чому я і користувався LinkedHashMap!

Дякую!


3
Ситуація справді прикро. Ось запит на функцію (низький пріоритет), який забезпечить необхідне: bugs.sun.com/view_bug.do?bug_id=6266354
Кевін Боррліон

stackoverflow.com/a/30984049/1808829 міг би вам допомогти
Аяз Аліфов

Відповіді:


159

Семантика LinkedHashMapдосі є тією картою, а не а LinkedList. Так, він зберігає порядок вставки, але це деталі реалізації, а не аспект його інтерфейсу.

Найшвидший спосіб отримати "перший" запис досі entrySet().iterator().next(). Отримати "останній" запис можливо, але це потягне за собою повторення всього набору записів, зателефонувавши .next()до тих пір, поки ви не досягнете останнього. while (iterator.hasNext()) { lastElement = iterator.next() }

редагувати : Однак якщо ви готові вийти за межі JavaSE API, Apache Commons Collections має власну LinkedMapреалізацію, яка має такі методи, як firstKeyі lastKeyякі роблять те, що ви шукаєте. Інтерфейс значно багатший.


Я повинен вставити запис (ключ, значення) як перший запис у моїй пов'язаній карті; АБО щось на зразок вставки на основі індексу. це можливо з колекцій Apache?
Канагавелу Сугумар

2
Посилання "LinkedMap", "firstKey" та "lastKey" є застарілими, фій.
Джош

Здається, ви насправді, можливо, навіть можете використовувати Commons LinkedMap для переміщення в зворотному порядку, отримуючи lastKey, а потім використовуючи попереднійKey після цього, щоб відступити назад ... приємно.
rogerdpack

Здається, ви насправді, можливо, навіть можете використовувати Commons LinkedMap для переміщення в зворотному порядку, отримуючи lastKey, а потім використовуючи попереднійKey після цього, щоб відступити назад ... приємно. Ви могли б навіть написати свій власний Iterable , якщо ви хочете використовувати його в Еогеасп петлі наприклад: stackoverflow.com/a/1098153/32453
rogerdpack

1
@skaffman, чи можете ви допомогти: що таке mylinkedmap.entrySet().iterator().next()складність у часі? Це О (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

Я знаю, що я прийшов занадто пізно, але хотів би запропонувати альтернативи, не щось надзвичайне, а деякі випадки, про які тут ніхто не згадувався. Якщо хтось не так сильно піклується про ефективність, але він хоче щось більше простоти (можливо, знайде останнє значення введення з одним рядком коду), все це стане досить спрощеним з приходом 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. Він також має оптимізацію для списків та сортованих наборів

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
Думаю, я додавав ClassCastExceptionби про catchвсяк випадок, якщо tailце не Entryпідклас (або майбутня реалізація).
Пол Боддінгтон

@PaulBoddington Я замінив це уловом усього - не рекомендується взагалі, але, мабуть, тут підходить.
assylias

7
а-а "брудна" відповідь :)
rogerdpack

6

Ще один спосіб отримати перший і останній запис LinkedHashMap - це використовувати метод "toArray" інтерфейсу Set.

Але я вважаю, що кращим підходом є повторення записів у наборі записів та отримання першого та останнього запису.

Використання методів масиву призводить до попередження форми "... потребує неперевіреної конверсії, щоб відповідати ...", яку неможливо виправити [але її можна придушити лише за допомогою анотації @SuppressWarnings ("невірно")].

Ось невеликий приклад продемонструвати використання методу "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 знову повторить хешмап :) Отже, його досить неефективний через небагато зайвих циклів витрачається на створення масиву та місця, виділеного для масиву.
Дурін

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. Таким чином, eldestEntry не буде оновлюватися в тих випадках, якщо після цього не буде викликано посилання.
vishr

@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) для пошуку останнього вставленого ключа.
Емад Агаї

1

Я рекомендую використовувати ConcurrentSkipListMap, який має firstKey()і lastKey()методи


1
ConcurrentSkipListMap потрібен компаратор (або природний компаратор), тому вам потрібно виконати додаткову роботу, щоб зберегти порядок, в якому ставляться записи.
AlikElzin-kilaka

HashMap і спеціально LinkedHashMap забезпечують доступ у середньому O (1) - всупереч SkipList, який забезпечує доступ у середньому O (logn).
AlikElzin-kilaka

"Карта сортована відповідно до природного впорядкування її ключів"

1

Для першого елемента використовуйте entrySet().iterator().next()та зупиняйте ітерацію після 1 ітерації. Для останнього найпростіший спосіб - зберегти ключ у змінній, коли ви робите map.put.


0

Хоча linkedHashMap не забезпечує жодного способу отримання першого, останнього чи будь-якого конкретного об'єкта.

Але досить тривіально отримати:

  • Map orderMap = новий 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;
}

Якщо вам знадобиться і останній елемент - я б розглядав, як змінити порядок своєї карти - зберігати її в змінній temp, отримати доступ до першого елемента на звороті карти (отже, це буде ваш останній елемент), вбити temp змінна.

Ось кілька хороших відповідей про те, як змінити хешмап:

Як повторити хешмап у зворотному порядку на 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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.