Java LinkedHashMap получить первую или последнюю запись


139

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

Но теперь я хочу получить значение ключа первым (первая введенная запись) или последним.

Должны ли быть метод , как first()и last()или что - то подобное?

Нужен ли мне итератор, чтобы получить первую запись ключа? Вот почему я использовал LinkedHashMap!

Спасибо!


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

Ответы:


159

Семантика по- LinkedHashMapпрежнему является семантикой карты, а не семантики LinkedList. Да, он сохраняет порядок вставки, но это деталь реализации, а не аспект его интерфейса.

Самый быстрый способ получить «первую» запись по-прежнему entrySet().iterator().next(). Получение «последней» записи возможно, но повлечет за собой итерации по всему набору записей, вызывая .next()до тех пор, пока вы не достигнете последней. while (iterator.hasNext()) { lastElement = iterator.next() }

edit : Однако, если вы хотите выйти за пределы API JavaSE, у Apache Commons Collections есть собственная LinkedMapреализация, в которой есть такие методы, как firstKeyи lastKey, которые делают то, что вы ищете. Интерфейс значительно богаче.


Я должен вставить запись (ключ, значение) в качестве первой записи в моей связанной карте; ИЛИ что-то вроде вставки на основе индекса. это возможно с коллекциями Apache?
Канагавелу Сугамар

2
Ссылки "LinkedMap", "firstKey" и "lastKey" устарели.
Джош

Похоже, вы на самом деле, может быть, даже можете использовать Commons LinkedMap, чтобы пройти по нему в обратном порядке, получив lastKey, а затем с помощью previousKey сделать шаг назад ... приятно.
rogerdpack

Похоже, вы на самом деле, может быть, даже можете использовать Commons LinkedMap, чтобы пройти по нему в обратном порядке, получив lastKey, а затем используя previousKey, чтобы сделать шаг назад ... приятно. Затем вы могли бы даже написать свой собственный Iterable, если вы хотите использовать его в циклах foreach, например: 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) в памяти.
Флориан Ф

@FlorianF Так что это зависит от того, насколько большой ваш список. Решение с использованием массива было бы быстрее и не скомпрометировало бы память, если коллекция не такая большая, в противном случае для ее итерации лучше потратить больше времени ... Интересно, есть ли и то и другое в качестве решения, особенно после Java 8.
skinny_jones

1
@skinny_jones: Почему решение с массивами будет быстрее? Он по-прежнему включает в себя итерации по всей карте, просто теперь итерация внутри метода JDK, а не в явном виде.
Руах

18

Я знаю, что я опоздал, но я хотел бы предложить несколько альтернатив, не что-то экстраординарное, но некоторые случаи, которые здесь не упоминались. В случае, если кто-то не очень заботится об эффективности, но хочет чего-то более простого (возможно, найдет значение последней записи с одной строкой кода), все это будет довольно упрощено с появлением Java 8 . Я приведу несколько полезных сценариев.

Ради полноты я сравниваю эти альтернативы с решением массивов, которые уже упоминались в этом посте другими пользователями. Я суммирую все случаи, и я думаю, что они будут полезны (когда производительность имеет значение или нет), особенно для новых разработчиков, всегда зависит от вопроса каждой проблемы

Возможные альтернативы

Использование метода Array

Я взял это из предыдущего ответа, чтобы сделать следующие сравнения. Это решение принадлежит @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. Он также имеет некоторую оптимизацию для списков и 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

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 («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 снова будет выполнять итерации по хэш-карте :) Так что он довольно неэффективен из-за нескольких лишних циклов, потраченных впустую при создании массива, и пространства, выделенного для массива.
Дурин

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 не будет обновляться в этих случаях, если только после этого не вызывается put.
Вишр

@vishr старшая запись обновляется при вызове пут. (получить и поставить для доступа порядок 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 требует компаратора (или натурального компаратора), поэтому вам нужно проделать дополнительную работу, чтобы сохранить порядок, в котором размещаются записи.
Алик Эльзин-килака

HashMap и, в частности, LinkedHashMap обеспечивает доступ в среднем за O (1) - в отличие от SkipList, который обеспечивает доступ в среднем за O (logn).
Алик Эльзин-килака

«Карта отсортирована в соответствии с естественным порядком ее ключей»

1

Для использования первого элемента entrySet().iterator().next()и прекращения итерации после 1 итерации. Для последнего самый простой способ - сохранить ключ в переменной всякий раз, когда вы выполняете map.put.


0

Хотя connectedHashMap не предоставляет какого-либо метода для получения первого, последнего или какого-либо конкретного объекта.

Но это довольно тривиально, чтобы получить:

  • Карта orderMap = new LinkedHashMap ();
    Set 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;
}

Если вам также нужен последний элемент - я посмотрю, как изменить порядок вашей карты - сохраните его во временной переменной, получите доступ к первому элементу на обратной карте (следовательно, это будет ваш последний элемент), убейте временная переменная

Вот несколько хороших ответов о том, как изменить порядок хэш-карт:

Как перебрать hashmap в обратном порядке в 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.