Java LinkedHashMap รับรายการแรกหรือรายการสุดท้าย


146

ฉันใช้LinkedHashMapเพราะลำดับการป้อนคีย์ในแผนที่เป็นสิ่งสำคัญ

แต่ตอนนี้ฉันต้องการรับค่าของคีย์ตั้งแต่แรก (รายการที่ป้อนครั้งแรก) หรือรายการสุดท้าย

ควรมีวิธีการเหมือนfirst()และlast()หรืออย่างนั้นหรือไม่?

ฉันจำเป็นต้องมีตัวทำซ้ำเพื่อรับรายการคีย์แรกหรือไม่? นั่นคือเหตุผลที่ฉันใช้LinkedHashMap!

ขอบคุณ!


3
สถานการณ์นั้นโชคร้ายแน่นอน นี่คือคำขอคุณลักษณะ (ลำดับความสำคัญต่ำ) ที่จะให้สิ่งที่คุณต้องการ: bugs.sun.com/view_bug.do?bug_id=6266354
— Kevin Bourrillion

stackoverflow.com/a/30984049/1808829ช่วยคุณได้
— Ayaz Alifov

คำตอบ:


167

ความหมายของLinkedHashMapแผนที่ยังคงเป็นความหมายของแผนที่แทนที่จะเป็นความหมายของไฟล์LinkedList. มันยังคงลำดับการแทรกใช่ แต่นั่นคือรายละเอียดการใช้งานมากกว่าแง่มุมของอินเทอร์เฟซ

วิธีที่เร็วที่สุดในการเข้า "ครั้งแรก" ยังคงentrySet().iterator().next()อยู่ การรับรายการ "สุดท้าย" เป็นไปได้ แต่จะทำให้เกิดการวนซ้ำในรายการทั้งหมดโดยการโทร.next()จนกว่าคุณจะไปถึงรายการสุดท้าย while (iterator.hasNext()) { lastElement = iterator.next() }

แก้ไข : อย่างไรก็ตามหากคุณต้องการที่จะก้าวไปไกลกว่า JavaSE API Apache Commons Collectionsก็มีการLinkedMapใช้งานของตัวเองซึ่งมีวิธีการเช่นfirstKeyและlastKeyซึ่งจะทำในสิ่งที่คุณกำลังมองหา อินเทอร์เฟซมีความสมบูรณ์มากขึ้น


ฉันต้องแทรกรายการ (คีย์ค่า) เป็นรายการแรกในแผนที่ที่เชื่อมโยงของฉัน หรือบางอย่างเช่นการแทรกตามดัชนี เป็นไปได้ไหมกับคอลเลกชัน Apache
— Kanagavelu Sugumar

2
ลิงก์ "LinkedMap" "firstKey" และ "lastKey" ไม่อัปเดต fyi
— Josh

ปรากฏว่าคุณอาจใช้ Commons LinkedMap เพื่อสำรวจในลำดับย้อนกลับได้โดยรับ lastKey จากนั้นใช้ previousKey หลังจากนั้นเพื่อถอยหลัง ...
— rogerdpack

ปรากฏว่าคุณอาจใช้ Commons LinkedMap เพื่อสำรวจในลำดับย้อนกลับได้โดยรับ lastKey จากนั้นใช้ previousKey หลังจากนั้นเพื่อถอยหลัง ... จากนั้นคุณสามารถเขียน Iterable ของคุณเองได้หากต้องการใช้ใน foreach loops เช่นstackoverflow.com/a/1098153/32453
— rogerdpack

1
@skaffman คุณช่วยได้mylinkedmap.entrySet().iterator().next()ไหม: ความซับซ้อนของเวลาคืออะไร? เป็น O (1) หรือไม่?
— tkrishtop

27

คุณลองทำสิ่งต่างๆเช่น (เพื่อรับรายการสุดท้าย):

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

5
ยังเร็วกว่าที่จะทำซ้ำและเก็บรายการสุดท้ายไว้ T last = null ; for( T item : linkedHashMap.values() ) last = item; หรืออะไรทำนองนั้น. เป็น O (N) ในเวลา แต่ O (1) อยู่ในหน่วยความจำ
— Florian F

@FlorianF ดังนั้นมันขึ้นอยู่กับว่ารายการของคุณใหญ่แค่ไหน โซลูชันอาร์เรย์จะเร็วขึ้นและจะไม่กระทบกับหน่วยความจำหากคอลเลกชันไม่ใหญ่ขนาดนั้นมิฉะนั้นจะดีกว่าในการทำซ้ำผ่าน ... ฉันสงสัยว่ามีทั้งสองอย่างเป็นวิธีแก้ปัญหาหรือไม่โดยเฉพาะตั้งแต่ Java 8
— skinny_jones

2
@skinny_jones: ทำไมโซลูชันอาร์เรย์ถึงเร็วขึ้น? มันยังคงเกี่ยวข้องกับการทำซ้ำบนแผนที่ทั้งหมด แต่ตอนนี้การวนซ้ำอยู่ในเมธอด JDK แทนที่จะเป็นแบบชัดแจ้ง
— ruakh

20

ฉันรู้ว่าฉันมาสายเกินไป แต่ฉันต้องการเสนอทางเลือกอื่นไม่ใช่สิ่งที่พิเศษ แต่บางกรณีที่ไม่มีการกล่าวถึงที่นี่ ในกรณีที่มีคนไม่สนใจมากสำหรับประสิทธิภาพ แต่เขาต้องการบางสิ่งบางอย่างด้วยความเรียบง่ายมากขึ้น (อาจจะหาค่ารายการสุดท้ายกับหนึ่งบรรทัดของรหัส) ทั้งหมดนี้จะได้รับการง่ายมากกับการมาถึงของ 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 มีการเพิ่มประสิทธิภาพสำหรับ Lists และ 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ใน subclass (หรือการดำเนินงานในอนาคต)
— Paul Boddington

@PaulBoddington ฉันแทนที่ด้วยการจับทั้งหมด - ไม่แนะนำโดยทั่วไป แต่น่าจะเหมาะสมที่นี่
— assylias

7
อ่าคำตอบ "สกปรก" :)
— rogerdpack

6

อีกวิธีหนึ่งในการรับรายการแรกและรายการสุดท้ายของ LinkedHashMap คือการใช้toArray()วิธีการตั้งค่าอินเทอร์เฟซ

แต่ฉันคิดว่าการทำซ้ำรายการในชุดรายการและการรับรายการแรกและรายการสุดท้ายเป็นแนวทางที่ดีกว่า

การใช้เมธอดอาร์เรย์นำไปสู่คำเตือนของรูปแบบ"... ต้องมีการแปลงที่ไม่เลือกเพื่อให้สอดคล้องกับ ... "ซึ่งไม่สามารถแก้ไขได้ [แต่สามารถระงับได้โดยใช้คำอธิบายประกอบ@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 จะวนซ้ำบนแฮชแมปอีกครั้ง :) ดังนั้นจึงค่อนข้างไม่มีประสิทธิภาพมากขึ้นเนื่องจากมีรอบพิเศษสองสามรอบที่เสียไปในการสร้างอาร์เรย์และพื้นที่ที่จัดสรรให้กับอาร์เรย์
— Durin

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

รายการคนโตของ @vishr ได้รับการอัปเดตเมื่อเรียกใช้ put (รับและสั่งการเข้าถึง LinkedHashMap)
— Shiji.J

3

อาจจะเป็นเช่นนี้:

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) เพื่อค้นหาคีย์ที่แทรกล่าสุด
— Emad Aghayi

1

ฉันอยากจะแนะนำให้ใช้ConcurrentSkipListMapซึ่งมีfirstKey()และlastKey()วิธีการ


1
ConcurrentSkipListMap ต้องใช้ตัวเปรียบเทียบ (หรือตัวเปรียบเทียบธรรมชาติ) ดังนั้นคุณต้องทำงานพิเศษเพื่อบันทึกลำดับที่จะใส่รายการ
— AlikElzin-kilaka

HashMap และ LinkedHashMap โดยเฉพาะให้การเข้าถึงโดยเฉลี่ย O (1) - ตรงกันข้ามกับ SkipList ซึ่งให้การเข้าถึงโดยเฉลี่ย O (เข้าสู่ระบบ)
— AlikElzin-kilaka

1
"แผนที่เรียงตามลำดับตามธรรมชาติของคีย์"

1

สำหรับการใช้องค์ประกอบแรกentrySet().iterator().next()และหยุดการทำซ้ำหลังจากการทำซ้ำ 1 ครั้ง สำหรับวิธีสุดท้ายวิธีที่ง่ายที่สุดคือเก็บคีย์ในตัวแปรทุกครั้งที่คุณทำ map.put


0

ใช่ฉันเจอปัญหาเดียวกัน แต่โชคดีที่ฉันต้องการเพียงองค์ประกอบแรก ... - นี่คือสิ่งที่ฉันทำเพื่อมัน

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

หากคุณต้องการองค์ประกอบสุดท้ายเช่นกัน - ฉันจะดูวิธีย้อนลำดับของแผนที่ของคุณ - เก็บไว้ในตัวแปร 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;
}

0

แม้ว่า linkedHashMap จะไม่มีวิธีการใด ๆ ในการรับสิ่งแรกสุดท้ายหรือวัตถุเฉพาะใด ๆ

แต่มันเป็นเรื่องเล็กน้อยที่จะได้รับ:

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

ตอนนี้ใช้ตัววนซ้ำบนalวัตถุ คุณจะได้รับวัตถุใด ๆ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.