按照值对 Map<Key, Value> 进行排序

1881
我需要根据值对一个Map进行排序。
由于值不是唯一的,我发现自己需要将keySet转换为数组,并通过使用自定义比较器对该数组进行排序,以便根据与键关联的值进行排序。
有没有更简单的方法?

32
地图的目的不是为了排序,而是为了快速访问。对象相等的值会违反地图的约束条件。使用entry set,例如 List<Map.Entry<...>> list =new LinkedList(map.entrySet())Collections.sort .... 进行排序。 - Hannes
2
一个可能出现这种情况的案例是当我们尝试在Java中使用计数器(Map<Object,Integer>)时。按出现次数排序将成为常见操作。像Python这样的语言具有内置的计数器数据结构。对于Java中的另一种实现方式,此处提供了一个示例。 - demongolem
14
有很多使用排序映射的情况,这就是为什么在jdk中有TreeMap和ConcurrentSkipListMap的原因。 - alobodzk
8
TreeMap和ConcurrentSkipListMap会根据键进行排序。问题是如何按值排序。 - Peter
3
根据您的使用情况,保留一个重复的TreeMap,将值映射到键可能是合理的。例如,您的常规map可能为"a"->5,"b"->7。而您的“排序”map可以有5->“a”,7->“b”。您只需在不同的地方使用适当的map,并努力始终同时修改这两个map。虽然存在许多警告和假设,但对于某些情况而言,与所有依赖于主动排序您的值的答案相比,这可能是一种简单而有效的答案。 - rococo
显示剩余2条评论
65个回答

2
当我面对这种情况时,我会在侧边创建一个列表。如果你将它们放在自定义的Map实现中,它会有一种不错的感觉...你可以使用像下面这样的方法,在需要时执行排序。(注意:我没有真正测试过这个,但它可以编译...可能有一个小小的bug)
(如果你想按键和值排序,让类扩展TreeMap,不要定义访问器方法,并让mutators调用super.xxxxx而不是map_.xxxx)
package com.javadude.sample;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class SortedValueHashMap<K, V> implements Map<K, V> {
    private Map<K, V> map_ = new HashMap<K, V>();
    private List<V> valueList_ = new ArrayList<V>();
    private boolean needsSort_ = false;
    private Comparator<V> comparator_;

    public SortedValueHashMap() {
    }
    public SortedValueHashMap(List<V> valueList) {
        valueList_ = valueList;
    }

    public List<V> sortedValues() {
        if (needsSort_) {
            needsSort_ = false;
            Collections.sort(valueList_, comparator_);
        }
        return valueList_;
    }

    // mutators
    public void clear() {
        map_.clear();
        valueList_.clear();
        needsSort_ = false;
    }

    public V put(K key, V value) {
        valueList_.add(value);
        needsSort_ = true;
        return map_.put(key, value);
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        map_.putAll(m);
        valueList_.addAll(m.values());
        needsSort_ = true;
    }

    public V remove(Object key) {
        V value = map_.remove(key);
        valueList_.remove(value);
        return value;
    }

    // accessors
    public boolean containsKey(Object key)           { return map_.containsKey(key); }
    public boolean containsValue(Object value)       { return map_.containsValue(value); }
    public Set<java.util.Map.Entry<K, V>> entrySet() { return map_.entrySet(); }
    public boolean equals(Object o)                  { return map_.equals(o); }
    public V get(Object key)                         { return map_.get(key); }
    public int hashCode()                            { return map_.hashCode(); }
    public boolean isEmpty()                         { return map_.isEmpty(); }
    public Set<K> keySet()                           { return map_.keySet(); }
    public int size()                                { return map_.size(); }
    public Collection<V> values()                    { return map_.values(); }
}

2

posting my version of answer

List<Map.Entry<String, Integer>> list = new ArrayList<>(map.entrySet());
    Collections.sort(list, (obj1, obj2) -> obj2.getValue().compareTo(obj1.getValue()));
    Map<String, Integer> resultMap = new LinkedHashMap<>();
    list.forEach(arg0 -> {
        resultMap.put(arg0.getKey(), arg0.getValue());
    });
    System.out.println(resultMap);

2
使用链表
HTML标签保留。
//Create a list by HashMap
List<Map.Entry<String, Double>> list = new LinkedList<>(hashMap.entrySet());

//Sorting the list
Collections.sort(list, new Comparator<Map.Entry<String, Double>>() {
    public int compare(Map.Entry<String, Double> o1, Map.Entry<String, Double> o2) {
        return (o1.getValue()).compareTo(o2.getValue());
    }
});

//put data from sorted list to hashmap
HashMap<String, Double> sortedData = new LinkedHashMap<>();
for (Map.Entry<String, Double> data : list) {
    sortedData.put(data.getKey(), data.getValue());
}

System.out.print(sortedData);

2

这样做还有一个额外的好处,就是可以使用Java 8进行升序或降序排序

import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.toMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Utils {
    public static Map<String, Integer> sortMapBasedOnValues(Map<String, Integer> map, boolean descending) {
        int multiplyBy = (descending) ? -1: 1;
        Map<String, Integer> sorted =  map.entrySet().stream()
                .sorted(comparingInt(e -> multiplyBy * e.getValue() ))
                .collect(toMap(
                        Map.Entry::getKey, 
                        Map.Entry::getValue,
                        (a, b) -> { throw new AssertionError();},
                        LinkedHashMap::new
                    ));
        return sorted;
    }
}

1

我重写了devinmoore的方法,可以按照值对映射进行排序,而不使用迭代器:

public static Map<K, V> sortMapByValue(Map<K, V> inputMap) {

    Set<Entry<K, V>> set = inputMap.entrySet();
    List<Entry<K, V>> list = new ArrayList<Entry<K, V>>(set);

    Collections.sort(list, new Comparator<Map.Entry<K, V>>()
    {
        @Override
        public int compare(Entry<K, V> o1, Entry<K, V> o2) {
            return (o1.getValue()).compareTo( o2.getValue() );  //Ascending order
        }
    } );

    Map<K, V> sortedMap = new LinkedHashMap<>();

    for(Map.Entry<K, V> entry : list){
        sortedMap.put(entry.getKey(), entry.getValue());
    }

    return sortedMap;
}

注意:我们使用了LinkedHashMap作为输出映射,因为我们的列表已按值排序,现在我们应该按插入键和值的顺序将列表存储到输出映射中。因此,如果您使用例如TreeMap作为输出映射,则您的映射将再次按映射键排序!
这是主方法:
public static void main(String[] args) {
    Map<String, String> map = new HashMap<>();
    map.put("3", "three");
    map.put("1", "one");
    map.put("5", "five");
    System.out.println("Input Map:" + map);
    System.out.println("Sorted Map:" + sortMapByValue(map));
}

最终,这是输出结果:
Input Map:{1=one, 3=three, 5=five}
Sorted Map:{5=five, 1=one, 3=three}

1
我的解决方案是一种简单的方法,主要使用已有的API。我们利用Map的特性,通过entrySet()方法将其内容导出为Set。现在我们有一个包含Map.Entry对象的Set
好的,Set没有顺序,但我们可以将内容放入ArrayList中。它现在具有随机顺序,但我们无论如何都会对其进行排序。
由于ArrayList是一个Collection,所以我们现在使用Collections.sort()方法来使混乱有序化。因为我们的Map.Entry对象没有实现我们需要的比较方式,所以我们提供了一个自定义的Comparator
public static void main(String[] args) {
    HashMap<String, String> map = new HashMap<>();
    map.put("Z", "E");
    map.put("G", "A");
    map.put("D", "C");
    map.put("E", null);
    map.put("O", "C");
    map.put("L", "D");
    map.put("Q", "B");
    map.put("A", "F");
    map.put(null, "X");
    MapEntryComparator mapEntryComparator = new MapEntryComparator();

    List<Entry<String,String>> entryList = new ArrayList<>(map.entrySet());
    Collections.sort(entryList, mapEntryComparator);

    for (Entry<String, String> entry : entryList) {
        System.out.println(entry.getKey() + " : " + entry.getValue());
    }

}

什么是MapEntryComparator - SMUsamaShah

1
在TreeMap中,键按自然顺序排序。例如,如果您对数字进行排序,(请注意4的顺序)。 {0=0, 10=10, 20=20, 30=30, 4=4, 50=50, 60=60, 70=70} 为了解决这个问题,在Java8中,首先检查字符串长度,然后再进行比较。
Map<String, String> sortedMap = new TreeMap<>Comparator.comparingInt(String::length)
.thenComparing(Function.identity()));

{0=0, 4=4, 10=10, 20=20, 30=30, 50=50, 60=60, 70=70}

可能是一个键值对的集合,其中键和值都是数字。

1
我可以给你一个例子,但是不确定这是否符合你的需求。
map = {10 = 3, 11 = 1,12 = 2} 

假设你想要最常见的前两个键,即(10, 12)。那么最简单的方法就是使用一个PriorityQueue根据map的值进行排序。

PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> (map.get(a) - map.get(b));
for(int key: map.keySets()) {
   pq.add(key);
   if(pq.size() > 2) {
      pq.poll();
   }
}
// Now pq has the top 2 most frequent key based on value. It sorts the value. 

1
如果需要一个自动按值排序的Map数据结构,而不需要触发任何排序方法或显式传递到实用程序,则以下解决方案可能适用:
(1)org.drools.chance.core.util.ValueSortedMap(JBoss项目)在内部维护两个映射,一个用于查找,另一个用于维护排序后的值。与先前添加的答案非常相似,但可能是抽象和封装部分(包括复制机制)使其更安全地从外部使用。
(2)http://techblog.molindo.at/2008/11/java-map-sorted-by-value.html 避免维护两个映射,而是依赖/扩展自Apache Common的LinkedMap。(博客作者的注释:因为这里的所有代码都是公共领域):
// required to access LinkEntry.before and LinkEntry.after
package org.apache.commons.collections.map;

// SNIP: imports

/**
* map implementation based on LinkedMap that maintains a sorted list of
* values for iteration
*/
public class ValueSortedHashMap extends LinkedMap {
    private final boolean _asc;

    // don't use super()!
    public ValueSortedHashMap(final boolean asc) {
        super(DEFAULT_CAPACITY);
        _asc = asc;
    }

    // SNIP: some more constructors with initial capacity and the like

    protected void addEntry(final HashEntry entry, final int hashIndex) {
        final LinkEntry link = (LinkEntry) entry;
        insertSorted(link);
        data[hashIndex] = entry;
    }

    protected void updateEntry(final HashEntry entry, final Object newValue) {
        entry.setValue(newValue);
        final LinkEntry link = (LinkEntry) entry;
        link.before.after = link.after;
        link.after.before = link.before;
        link.after = link.before = null;
        insertSorted(link);
    }

    private void insertSorted(final LinkEntry link) {
        LinkEntry cur = header;
        // iterate whole list, could (should?) be replaced with quicksearch
        // start at end to optimize speed for in-order insertions
        while ((cur = cur.before) != header & amp; & amp; !insertAfter(cur, link)) {}
        link.after = cur.after;
        link.before = cur;
        cur.after.before = link;
        cur.after = link;
    }

    protected boolean insertAfter(final LinkEntry cur, final LinkEntry link) {
        if (_asc) {
            return ((Comparable) cur.getValue())
            .compareTo((V) link.getValue()) & lt; = 0;
        } else {
            return ((Comparable) cur.getValue())
            .compareTo((V) link.getValue()) & gt; = 0;
        }
    }

    public boolean isAscending() {
        return _asc;
    }
}

(3) 编写自定义 Map 或从 LinkedHashMap 扩展,仅在枚举期间(例如 values()keyset()entryset())根据需要进行排序。内部实现/行为与使用此类的实现分离,但对于此类的客户端,当请求枚举时,值始终按排序顺序显示。该类希望如果所有 put 操作在枚举之前已完成,则大多数情况下将发生排序。排序方法采用了本问题先前答案中的一些方法。

public class SortByValueMap<K, V> implements Map<K, V> {

    private boolean isSortingNeeded = false;

    private final Map<K, V> map = new LinkedHashMap<>();

    @Override
    public V put(K key, V value) {
        isSortingNeeded = true;
        return map.put(key, value);
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> map) {
        isSortingNeeded = true;
        map.putAll(map);
    }

    @Override
    public Set<K> keySet() {
        sort();
        return map.keySet();
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        sort();
        return map.entrySet();
    }

    @Override
    public Collection<V> values() {
        sort();
        return map.values();
    }

    private void sort() {
        if (!isSortingNeeded) {
            return;
        }

        List<Entry<K, V>> list = new ArrayList<>(size());

        for (Iterator<Map.Entry<K, V>> it = map.entrySet().iterator(); it.hasNext();) {
            Map.Entry<K, V> entry = it.next();
            list.add(entry);
            it.remove();
        }

        Collections.sort(list);

        for (Entry<K, V> entry : list) {
            map.put(entry.getKey(), entry.getValue());
        }

        isSortingNeeded = false;
    }

    @Override
    public String toString() {
        sort();
        return map.toString();
    }
}

(4) Guava提供了ImmutableMap.Builder.orderEntriesByValue(Comparator valueComparator),尽管生成的映射将是不可变的:

配置此构建器以根据指定比较器按值对条目进行排序。

排序顺序是稳定的,也就是说,如果两个条目的值相等,则先插入的条目将在构建的映射的迭代顺序中排在第一位。


1
使用Guava库:
public static <K,V extends Comparable<V>>SortedMap<K,V> sortByValue(Map<K,V> original){
    var comparator = Ordering.natural()
            .reverse() // highest first
            .nullsLast()
            .onResultOf(Functions.forMap(original, null))
            .compound(Ordering.usingToString());
    return ImmutableSortedMap.copyOf(original, comparator);
}

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