按值排序LinkedHashMap

7

如何使用值对LinkedHashMap进行排序?

是否有一种方法可以将条目插入到LinkedHashMap中,以便根据其值按顺序插入?


2
使用LinkedHashMap进行排序的目的是什么?也许可以使用TreeMap,并提供自己的Comparator来对值进行排序,而不是对键进行排序。 - Tom
整个“Linked” 的意义在于获得列表的好处,即索引访问。如果您想要默认排序,则需要一种基于树的数据结构。 - kolossus
4个回答

17
如何使用值对LinkedHashMap进行排序?
LinkedHashMap并没有被“排序”,它是按插入顺序“排序”的。
如果你的目标是重新排序Map,你可以做一些类似这样的事情:
static <K, V> void orderByValue(
        LinkedHashMap<K, V> m, final Comparator<? super V> c) {
    List<Map.Entry<K, V>> entries = new ArrayList<>(m.entrySet());

    Collections.sort(entries, new Comparator<Map.Entry<K, V>>() {
        @Override
        public int compare(Map.Entry<K, V> lhs, Map.Entry<K, V> rhs) {
            return c.compare(lhs.getValue(), rhs.getValue());
        }
    });

    m.clear();
    for(Map.Entry<K, V> e : entries) {
        m.put(e.getKey(), e.getValue());
    }
}
我们将所有条目放入List中,对List进行排序,然后按照新顺序将条目放回Map中。
以下是适用于Java 8的翻译:
static <K, V> void orderByValue(
        LinkedHashMap<K, V> m, Comparator<? super V> c) {
    List<Map.Entry<K, V>> entries = new ArrayList<>(m.entrySet());
    m.clear();
    entries.stream()
        .sorted(Comparator.comparing(Map.Entry::getValue, c))
        .forEachOrdered(e -> m.put(e.getKey(), e.getValue()));
}
< p >(出于好奇,可以概括为,尽管效率较低):< /p >
static <K, V> void orderByValue(
        LinkedHashMap<K, V> m, Comparator<? super V> c) {
    new ArrayList<>(m.keySet()).stream()
        .sorted(Comparator.comparing(m::get, c))
        .forEachOrdered(k -> m.put(k, m.remove(k)));
}

有没有一种方法可以根据值的顺序将条目插入到LinkedHashMap中?

没有。请参见上面的内容。LinkedHashMap没有排序功能。

如果您的目标是保持Map的排序,则需要使用TreeMap;但是这样做会存在问题。地图中的条目需要具有唯一值。请参见此处此处


1
优雅而简洁。Java 8版本怎么样 - 作为一个挑战。:D - OldCurmudgeon
@LuiggiMendoza 如果你的意思是使用一个临时的TreeMap来对LinkedHashMap进行排序(例如TreeMap<...> tm = new TreeMap<>(lhm, valueComparator); lhm.clear(); lhm.putAll(tm);),我不认为这是一个好主意。要么值必须是唯一的,要么你需要一个hack比较器 - Radiodef
更加优雅 -<微笑> - OldCurmudgeon
@OldCurmudgeon 当然,这个方法可以接受Map,但它不会有任何效果。我想如果Java有一个像OrderedMap<K, V>这样的接口,即使只是一个标记,LinkedHashMap也可以实现,那就太好了。 - Radiodef
在函数头中声明参数,如此写有什么意义呢 -> static <K, V> void orderByValue?我就是不明白...这种语法通常不是用于参数化类型吗?据我所知,这块似乎没有任何需要参数化的东西...有人能解释一下吗? - SebasSBM
@SebasSBM 它让我们以更类型安全的方式处理地图,而不需要我们知道其实际类型。在这种情况下,主要优点是它允许我们传递与地图值类型兼容的比较器。(Comparator<? super V>基本上就是一个Comparator<V>,但它对于您可以传递到方法中的内容有一些更宽松的限制。) 您可以通过使用原始类型编写等效的方法,但强烈不建议使用原始类型。 - Radiodef

2

我认为对LinkedHashMap按值进行排序的最佳方式是编写一个比较器,通过值来比较两个Map.Entry<K,V>对象,然后

Map.Entry<K,V>[] entries = (Map.Entry<K,V>[])map.entrySet().toArray();
Arrays.sort(entries, comparator);

比较器看起来会像这样:
Comparator<Map.Entry<K,V>> comparator = new Comparator<Map.Entry<K,V>>() {
    @Override
    public int compare(Map.Entry<K,V> o1, Map.Entry<K,V> o2) {
        return o1.getValue().compareTo(o2.getValue());
    }
};

基本上,这很明显:创建一个包含地图中所有键/值对的数组,然后对其进行排序。 注意:我还没有测试过。

至于第二个问题:这需要一种特殊的数据结构来维护有序的值。当您插入一个元素时,它会设置哈希表维护一个按插入顺序排列的双向链接元素列表设置某种AVL树以保持类似 TreeSet 的值的顺序。我不认为Java定义了这样的类,但也许有第三方库中的一个。最简单的方法可能是维护两个分离的结构:LinkedHashMap<K,V>TreeSet<Map.Entry<K,V>>


2

就像我前面提到的答案一样,LinkedHashMap并没有排序功能,它只保存插入顺序。您需要手动创建比较器。

现在的问题是希望按什么方式对Map进行排序?按整数键排序...

这里有一个例子:(Tree Map)

默认比较

           // Create a hash map
          TreeMap tm = new TreeMap();
          // Put elements to the map
          tm.put("person1", new Double(1));
          tm.put("person2", new Double(2));
          tm.put("person3", new Double(3));
          tm.put("person4", new Double(4));
          tm.put("person5", new Double(5));
          
          // Get a set of the entries
          Set set = tm.entrySet();
          // Get an iterator
          Iterator i = set.iterator();
          // Display elements
          while(i.hasNext()) {
             Map.Entry me = (Map.Entry)i.next();
             System.out.print(me.getKey() + ": ");
             System.out.println(me.getValue());
          }
          System.out.println();
          
          // Deposit 1000 into person5's account
          double balance = ((Double)tm.get("person5")).doubleValue();
          tm.put("person5", new Double(balance + 1000));
          System.out.println("person5's new balance: " +
          tm.get("person5"));

这个树将按照键的自然顺序进行排序,即 person1 抛出 person5。

    person1: 1.00
    person2: 2.00
    person3: 3.00
    person4: 4.00
    person5: 5.00
    person5's new balance: 1005.00

使用自定义比较器:

public class MyTMCompUserDefine {
 
    public static void main(String a[]){
        //By using name comparator (String comparison)
        TreeMap<Empl,String> tm = new TreeMap<Empl, String>(new MyNameComp());
        tm.put(new Empl("Ram",3000), "RAM");
        tm.put(new Empl("John",6000), "JOHN");
        tm.put(new Empl("Crish",2000), "CRISH");
        tm.put(new Empl("Tom",2400), "TOM");
        Set<Empl> keys = tm.keySet();
        for(Empl key:keys){
            System.out.println(key+" ==> "+tm.get(key));
        }
        System.out.println("===================================");
        //By using salary comparator (int comparison)
        TreeMap<Empl,String> trmap = new TreeMap<Empl, String>(new MySalaryComp());
        trmap.put(new Empl("Ram",3000), "RAM");
        trmap.put(new Empl("John",6000), "JOHN");
        trmap.put(new Empl("Crish",2000), "CRISH");
        trmap.put(new Empl("Tom",2400), "TOM");
        Set<Empl> ks = trmap.keySet();
        for(Empl key:ks){
            System.out.println(key+" ==> "+trmap.get(key));
        }
    }
}
 
class MyNameComp implements Comparator<Empl>{
 
    @Override
    public int compare(Empl e1, Empl e2) {
        return e1.getName().compareTo(e2.getName());
    }
}
 
class MySalaryComp implements Comparator<Empl>{
 
    @Override
    public int compare(Empl e1, Empl e2) {
        if(e1.getSalary() > e2.getSalary()){
            return 1;
        } else {
            return -1;
        }
    }
}
 
class Empl{
     
    private String name;
    private int salary;
     
    public Empl(String n, int s){
        this.name = n;
        this.salary = s;
    }
     
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getSalary() {
        return salary;
    }
    public void setSalary(int salary) {
        this.salary = salary;
    }
    public String toString(){
        return "Name: "+this.name+"-- Salary: "+this.salary;
    }
}

Output:
Name: Crish-- Salary: 2000 ==> CRISH
Name: John-- Salary: 6000 ==> JOHN
Name: Ram-- Salary: 3000 ==> RAM
Name: Tom-- Salary: 2400 ==> TOM
===================================
Name: Crish-- Salary: 2000 ==> CRISH
Name: Tom-- Salary: 2400 ==> TOM
Name: Ram-- Salary: 3000 ==> RAM
Name: John-- Salary: 6000 ==> JOHN

1
我想按值排序,所以如果我要覆盖默认排序,创建树映射就没有意义。 - user3922757

0
将LinkedHashMap转换为排序后的List,然后按照你的需求进行操作。
        LinkedHashMap<String, BigDecimal> topCosts = data.getTopCosts();
        List<Map.Entry<String, BigDecimal>> entries = topCosts.entrySet()
            .stream()
            .sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue())) // desc
            .collect(Collectors.toList());
        for (Map.Entry<String, BigDecimal> entry : entries) {
            // do somting..
        }
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接