Java LinkedHashMap có được mục nhập đầu tiên hoặc cuối cùng


138

Tôi đã sử dụng LinkedHashMapvì điều quan trọng là thứ tự các phím được nhập trong bản đồ.

Nhưng bây giờ tôi muốn lấy giá trị của khóa ở vị trí đầu tiên (mục nhập đầu tiên được nhập) hoặc cuối cùng.

Có nên có một phương pháp như first()last()hoặc một cái gì đó như thế?

Tôi có cần phải có một trình vòng lặp để có được mục nhập khóa đầu tiên không? Đó là lý do tại sao tôi sử dụng LinkedHashMap!

Cảm ơn!


3
Tình hình thực sự đáng tiếc. Dưới đây là yêu cầu tính năng (mức độ ưu tiên thấp) sẽ cung cấp những gì bạn cần: bug.sun.com/view_orms.do?orms_id=6266354
Kevin Bourrillion

Câu trả lời:


158

Các ngữ nghĩa LinkedHashMapvẫn là của Bản đồ, hơn là của a LinkedList. Nó giữ lại thứ tự chèn, vâng, nhưng đó là một chi tiết triển khai, chứ không phải là một khía cạnh của giao diện của nó.

Cách nhanh nhất để có được mục "đầu tiên" vẫn là entrySet().iterator().next(). Có được mục nhập "cuối cùng" là có thể, nhưng sẽ đòi hỏi phải lặp đi lặp lại trên toàn bộ mục nhập bằng cách gọi .next()cho đến khi bạn đạt đến mục cuối cùng. while (iterator.hasNext()) { lastElement = iterator.next() }

chỉnh sửa : Tuy nhiên, nếu bạn sẵn sàng vượt ra ngoài API JavaSE, Bộ sưu tập Apache CommonsLinkedMaptriển khai riêng , có các phương thức như firstKeylastKey, thực hiện những gì bạn đang tìm kiếm. Giao diện phong phú hơn đáng kể.


Tôi phải chèn mục nhập (khóa, giá trị) làm mục nhập đầu tiên trong bản đồ được liên kết của mình; HOẶC một cái gì đó như chèn dựa trên chỉ mục. điều đó có khả thi với các bộ sưu tập của Apache không?
Kanagavelu Sugumar

2
Các liên kết "LinkedMap", "firstKey" và "lastKey" đã cũ, fyi.
Josh

Xuất hiện, bạn thực sự thậm chí có thể sử dụng Commons LinkedMap để duyệt theo thứ tự ngược lại, bằng cách lấy lastKey sau đó sử dụng trướcKey sau đó để lùi lại ... tốt đẹp.
rogerdpack

Xuất hiện, bạn thực sự thậm chí có thể sử dụng Commons LinkedMap để duyệt theo thứ tự ngược lại, bằng cách lấy lastKey sau đó sử dụng trướcKey sau đó để lùi lại ... tốt đẹp. Sau đó, bạn thậm chí có thể viết Iterable của riêng mình nếu bạn muốn sử dụng nó trong các vòng lặp foreach ex: stackoverflow.com/a/1098153/32453
rogerdpack 9/12/2015

1
@skaffman, bạn có thể vui lòng giúp đỡ: mylinkedmap.entrySet().iterator().next()thời gian phức tạp là gì? Có phải là O (1) không?
tkrishtop

25

Bạn có thể thử làm một cái gì đó như (để có được mục cuối cùng):

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

5
Nó vẫn nhanh hơn để lặp lại và giữ mục cuối cùng. T last = null ; for( T item : linkedHashMap.values() ) last = item; Hoặc điều tương tự. Đó là O (N) trong thời gian nhưng O (1) trong bộ nhớ.
Florian F

@FlorianF Vì vậy, nó phụ thuộc vào danh sách của bạn lớn như thế nào. Giải pháp mảng sẽ nhanh hơn và sẽ không ảnh hưởng đến bộ nhớ nếu bộ sưu tập không lớn, nếu không thì sẽ mất nhiều thời gian hơn để lặp lại nó ... Tôi tự hỏi liệu có một chút cả hai như một giải pháp, đặc biệt là từ Java 8.
skinny_jones

1
@ Dany_jones: Tại sao giải pháp mảng sẽ nhanh hơn? Nó vẫn liên quan đến việc lặp lại trên toàn bản đồ, chỉ là bây giờ việc lặp lại nằm trong một phương thức JDK thay vì rõ ràng.
ruakh

17

Tôi biết rằng tôi đã đến quá muộn nhưng tôi muốn đưa ra một số lựa chọn thay thế, không phải là điều gì đó phi thường nhưng một số trường hợp không được đề cập ở đây. Trong trường hợp ai đó không quan tâm nhiều đến hiệu quả nhưng anh ta muốn thứ gì đó đơn giản hơn (có thể tìm giá trị mục nhập cuối cùng với một dòng mã), tất cả điều này sẽ trở nên khá đơn giản với sự xuất hiện của Java 8 . Tôi cung cấp một số kịch bản hữu ích.

Để hoàn thiện, tôi so sánh các lựa chọn thay thế này với giải pháp mảng đã được đề cập trong bài đăng này của người dùng khác. Tôi tổng hợp tất cả các trường hợp và tôi nghĩ rằng chúng sẽ hữu ích (khi hiệu suất có vấn đề hay không) đặc biệt đối với các nhà phát triển mới, luôn phụ thuộc vào vấn đề của từng vấn đề

Các lựa chọn thay thế có thể

Sử dụng phương thức Array

Tôi lấy nó từ câu trả lời trước để so sánh theo sau. Giải pháp này thuộc về @feresr.

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

Sử dụng phương thức ArrayList

Tương tự như giải pháp đầu tiên với hiệu suất khác nhau một chút

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

Phương pháp giảm

Phương pháp này sẽ giảm tập hợp các phần tử cho đến khi nhận được phần tử cuối cùng của luồng. Ngoài ra, nó sẽ chỉ trả về kết quả xác định

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

Phương pháp bỏ qua

Phương thức này sẽ lấy phần tử cuối cùng của luồng bằng cách bỏ qua tất cả các phần tử trước nó

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

Thay thế lặp

Iterables.getLast từ Google ổi. Nó cũng có một số tối ưu hóa cho Danh sách và Sắp xếp

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

Đây là mã nguồn đầy đủ

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

Đây là đầu ra với hiệu suất của từng phương thức

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

LinkedHashMaptriển khai hiện tại (Java 8) theo dõi đuôi của nó. Nếu hiệu suất là mối quan tâm và / hoặc bản đồ có kích thước lớn, bạn có thể truy cập vào trường đó thông qua sự phản chiếu.

Bởi vì việc thực hiện có thể thay đổi, có lẽ cũng nên có một chiến lược dự phòng. Bạn có thể muốn đăng nhập một cái gì đó nếu một ngoại lệ được ném để bạn biết rằng việc thực hiện đã thay đổi.

Nó có thể trông giống như:

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
Tôi nghĩ rằng tôi sẽ thêm ClassCastExceptionvào catchchỉ trong trường hợp tailkhông phải là Entrytrong một lớp con (hoặc triển khai trong tương lai).
Paul Boddington

@PaulBoddington Tôi đã thay thế nó bằng một cái bắt tất cả - không được đề xuất nói chung nhưng có lẽ thích hợp ở đây.
assylias

7
ahh câu trả lời "bẩn" :)
rogerdpack

6

Một cách nữa để có được mục nhập đầu tiên và cuối cùng của LinkedHashMap là sử dụng phương thức "toArray" của giao diện Set.

Nhưng tôi nghĩ lặp đi lặp lại các mục trong bộ mục nhập và nhận mục đầu tiên và cuối cùng là một cách tiếp cận tốt hơn.

Việc sử dụng các phương thức mảng dẫn đến cảnh báo dạng "... cần chuyển đổi không được kiểm tra để tuân thủ ..." không thể sửa được [nhưng chỉ có thể được loại bỏ bằng cách sử dụng chú thích @SuppressWarnings ("không được kiểm tra")].

Dưới đây là một ví dụ nhỏ để chứng minh việc sử dụng phương thức "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
chính phương thức toArray sẽ lặp lại trên hashmap một lần nữa :) Vì vậy, nó khá kém hiệu quả hơn do một vài chu kỳ bổ sung bị lãng phí trong việc tạo mảng và không gian được phân bổ cho mảng.
Durin

5

Hơi bẩn một chút, nhưng bạn có thể ghi đè lên removeEldestEntryphương thức LinkedHashMap, phương thức này có thể phù hợp với bạn để làm thành viên ẩn danh riêng tư:

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

Vì vậy, bạn sẽ luôn có thể có được mục đầu tiên tại eldestthành viên của bạn . Nó sẽ được cập nhật mỗi khi bạn thực hiện a put.

Nó cũng dễ dàng ghi đè putvà thiết lập youngest...

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

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

Tất cả bị phá vỡ khi bạn bắt đầu loại bỏ các mục mặc dù; chưa tìm ra cách để loại bỏ điều đó.

Thật khó chịu khi bạn không thể truy cập vào đầu hoặc đuôi một cách hợp lý ...


Thử tốt, nhưng mục lớn nhất được cập nhật khi map.get được gọi. Vì vậy, eldestEntry sẽ không được cập nhật trong những trường hợp đó, trừ khi lệnh được gọi sau đó.
vishr

Mục nhập lớn nhất của @vishr được cập nhật khi lệnh được gọi. (nhận và đặt để truy cập thứ tự LinkedHashMap)
Shiji.J

2

Có lẽ một cái gì đó như thế này:

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

Gợi ý:

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

nó đưa ra khóa được chèn đầu tiên trên bản đồ. Tìm khóa đầu tiên sẽ ở O (1). Vấn đề ở đây là tìm cách trong O (1) để tìm khóa được chèn cuối cùng.
Emad Aghayi

1

Tôi sẽ khuyên bạn nên sử dụng ConcảnSkipListMapfirstKey()lastKey()phương thức


1
ConcurrencySkipListMap yêu cầu một bộ so sánh (hoặc bộ so sánh tự nhiên), vì vậy bạn cần thực hiện thêm công việc để lưu thứ tự các mục nhập được đặt.
AlikElzin-kilaka

HashMap và cụ thể là LinkedHashMap cung cấp quyền truy cập trung bình là O (1) - trái với SkipList, cung cấp quyền truy cập trung bình là O (logn).
AlikElzin-kilaka

"Bản đồ được sắp xếp theo thứ tự tự nhiên của các phím"

1

Để sử dụng phần tử đầu tiên entrySet().iterator().next()và dừng lặp lại sau 1 lần lặp. Đối với cách cuối cùng, cách dễ nhất là bảo toàn khóa trong một biến bất cứ khi nào bạn thực hiện map.put.


0

Mặc dù LinkedHashMap không cung cấp bất kỳ phương thức nào để có được đầu tiên, cuối cùng hoặc bất kỳ đối tượng cụ thể nào.

Nhưng nó khá tầm thường để có được:

  • Bản đồ orderMap = new LinkedHashMap ();
    Đặt al = orderMap.keySet ();

bây giờ sử dụng iterator trên al object; bạn có thể nhận được bất kỳ đối tượng.


0

Phải, tôi đã gặp vấn đề tương tự, nhưng may mắn là tôi chỉ cần yếu tố đầu tiên ... - Đây là những gì tôi đã làm cho nó.

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

Nếu bạn cũng cần phần tử cuối cùng - Tôi sẽ xem xét cách đảo ngược thứ tự bản đồ của bạn - lưu trữ nó trong một biến tạm thời, truy cập phần tử đầu tiên trong bản đồ đảo ngược (do đó nó sẽ là phần tử cuối cùng của bạn), tiêu diệt biến tạm thời.

Dưới đây là một số câu trả lời hay về cách đảo ngược thứ tự hashmap:

Cách lặp lại hashmap theo thứ tự ngược trong Java

Nếu bạn sử dụng trợ giúp từ liên kết trên, vui lòng bỏ phiếu cho họ :) Hy vọng điều này có thể giúp được ai đó.


0

đúng, bạn phải liệt kê thủ công bộ khóa cho đến khi kết thúc danh sách được liên kết, sau đó lấy mục nhập bằng khóa và trả lại mục này.


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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.