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.