我对Java比较陌生,经常发现需要对值进行Map<Key,Value>排序。

由于这些值不是唯一的,我发现自己将keySet转换为一个数组,并使用自定义比较器通过数组排序对该数组进行排序,该比较器根据与该键关联的值进行排序。

有没有更简单的方法?


当前回答

这种方法正好能达到目的。(“挫折”是Values必须实现java.util.Comparable接口)

  /**

 * Sort a map according to values.

 * @param <K> the key of the map.
 * @param <V> the value to sort according to.
 * @param mapToSort the map to sort.

 * @return a map sorted on the values.

 */ 
public static <K, V extends Comparable< ? super V>> Map<K, V>
sortMapByValues(final Map <K, V> mapToSort)
{
    List<Map.Entry<K, V>> entries =
        new ArrayList<Map.Entry<K, V>>(mapToSort.size());  

    entries.addAll(mapToSort.entrySet());

    Collections.sort(entries,
                     new Comparator<Map.Entry<K, V>>()
    {
        @Override
        public int compare(
               final Map.Entry<K, V> entry1,
               final Map.Entry<K, V> entry2)
        {
            return entry1.getValue().compareTo(entry2.getValue());
        }
    });      

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

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

    }      

    return sortedMap;

}

http://javawithswaranga.blogspot.com/2011/06/generic-method-to-sort-hashmap.html

其他回答

三个单行答案。。。

我会使用GoogleCollectionsGuava来实现这一点-如果你的价值观是可比较的,那么你可以使用

valueComparator = Ordering.natural().onResultOf(Functions.forMap(map))

这将为地图创建一个函数(对象)[将任何键作为输入,返回相应的值],然后对它们应用自然(可比较)排序[值]。

如果它们不具有可比性,那么您需要按照

valueComparator = Ordering.from(comparator).onResultOf(Functions.forMap(map)) 

这些可以应用于TreeMap(因为Ordering扩展了Comparator),或者在排序后应用于LinkedHashMap

注意:如果要使用TreeMap,请记住,如果比较==0,则该项已在列表中(如果有多个值进行比较,则会发生这种情况)。为了缓解这种情况,您可以像这样将键添加到比较器中(假设键和值是可比较的):

valueComparator = Ordering.natural().onResultOf(Functions.forMap(map)).compound(Ordering.natural())

=对键映射的值应用自然排序,并将其与键的自然排序组合

请注意,如果您的键与0比较,这仍然不起作用,但这对于大多数可比较的项来说应该足够了(因为hashCode、equals和compareTo通常是同步的…)

请参见Ordering.onResultOf()和Functions.forMap()。

实施

现在我们有了一个比较器,它可以满足我们的需要,我们需要从中得到一个结果。

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

现在,这很可能奏效,但:

需要完成一张完整的地图不要在TreeMap上尝试上面的比较器;当插入的键在put之后才有值时,尝试比较它是没有意义的,也就是说,它会很快断开

第1点对我来说有点破坏交易;google集合非常懒惰(这很好:你几乎可以在一瞬间完成所有操作;真正的工作是在你开始使用结果时完成的),这需要复制整个地图!

“完整”答案/按值排序的实时地图

不过别担心;如果你痴迷于以这种方式对“实时”地图进行排序,那么你可以用以下疯狂的方式解决上述问题,而不是其中一个,而是两个(!):

注意:这在2012年6月发生了重大变化-以前的代码永远无法工作:需要内部HashMap来查找值,而不需要在TreeMap.get()->compare()和compare(()->get()之间创建无限循环

import static org.junit.Assert.assertEquals;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

import com.google.common.base.Functions;
import com.google.common.collect.Ordering;

class ValueComparableMap<K extends Comparable<K>,V> extends TreeMap<K,V> {
    //A map for doing lookups on the keys for comparison so we don't get infinite loops
    private final Map<K, V> valueMap;

    ValueComparableMap(final Ordering<? super V> partialValueOrdering) {
        this(partialValueOrdering, new HashMap<K,V>());
    }

    private ValueComparableMap(Ordering<? super V> partialValueOrdering,
            HashMap<K, V> valueMap) {
        super(partialValueOrdering //Apply the value ordering
                .onResultOf(Functions.forMap(valueMap)) //On the result of getting the value for the key from the map
                .compound(Ordering.natural())); //as well as ensuring that the keys don't get clobbered
        this.valueMap = valueMap;
    }

    public V put(K k, V v) {
        if (valueMap.containsKey(k)){
            //remove the key in the sorted set before adding the key again
            remove(k);
        }
        valueMap.put(k,v); //To get "real" unsorted values for the comparator
        return super.put(k, v); //Put it in value order
    }

    public static void main(String[] args){
        TreeMap<String, Integer> map = new ValueComparableMap<String, Integer>(Ordering.natural());
        map.put("a", 5);
        map.put("b", 1);
        map.put("c", 3);
        assertEquals("b",map.firstKey());
        assertEquals("a",map.lastKey());
        map.put("d",0);
        assertEquals("d",map.firstKey());
        //ensure it's still a map (by overwriting a key, but with a new value) 
        map.put("d", 2);
        assertEquals("b", map.firstKey());
        //Ensure multiple values do not clobber keys
        map.put("e", 2);
        assertEquals(5, map.size());
        assertEquals(2, (int) map.get("e"));
        assertEquals(2, (int) map.get("d"));
    }
 }

当我们放入时,我们确保哈希映射具有比较器的值,然后将其放入TreeSet进行排序。但在此之前,我们检查哈希图,看看该键实际上不是重复的。此外,我们创建的比较器还将包括关键字,这样重复的值就不会删除非重复的关键字(由于==比较)。这两项对于确保地图合同得到遵守至关重要;如果你认为你不想这样,那么你几乎就要完全颠倒地图了(地图<V,K>)。

构造函数需要调用为

 new ValueComparableMap(Ordering.natural());
 //or
 new ValueComparableMap(Ordering.from(comparator));
    Map<String, Integer> map = new HashMap<>();
    map.put("b", 2);
    map.put("a", 1);
    map.put("d", 4);
    map.put("c", 3);
    
    // ----- Using Java 7 -------------------
    List<Map.Entry<String, Integer>> entries = new ArrayList<>(map.entrySet());
    Collections.sort(entries, (o1, o2) -> o1.getValue().compareTo(o2.getValue()));
    System.out.println(entries); // [a=1, b=2, c=3, d=4]


    // ----- Using Java 8 Stream API --------
   map.entrySet().stream().sorted(Map.Entry.comparingByValue()).forEach(System.out::println); // {a=1, b=2, c=3, d=4}

    

Java 8提供了一个新的答案:将条目转换为流,并使用Map中的比较器组合符。条目:

Stream<Map.Entry<K,V>> sorted =
    map.entrySet().stream()
       .sorted(Map.Entry.comparingByValue());

这将允许您使用按值升序排序的条目。如果您想要递减值,只需反转比较器:

Stream<Map.Entry<K,V>> sorted =
    map.entrySet().stream()
       .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));

如果这些值不可比较,则可以传递显式比较器:

Stream<Map.Entry<K,V>> sorted =
    map.entrySet().stream()
       .sorted(Map.Entry.comparingByValue(comparator));

然后,您可以继续使用其他流操作来使用数据。例如,如果要在新地图中显示前10名:

Map<K,V> topTen =
    map.entrySet().stream()
       .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
       .limit(10)
       .collect(Collectors.toMap(
          Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));

上面看到的LinkedHashMap按插入顺序迭代条目。

或打印到System.out:

map.entrySet().stream()
   .sorted(Map.Entry.comparingByValue())
   .forEach(System.out::println);

我认为最好的方法是使用特殊的数据结构。您可以考虑TreeMap,但在一般情况下,值可能不是唯一的。因此,您的选择是PriorityQueue:

public static <K, V> Iterator<Map.Entry<K, V>> sortByValue(
                                              Map<K, V> map,
                                              Comparator<V> valueComparator) {
    Queue<Map.Entry<K, V>> queue = new PriorityQueue<>((one, two) ->
                     valueComparator.compare(one.getValue(), two.getValue()));
    queue.addAll(map.entrySet());
    return queue.iterator();
}

这是Anthony答案的变体,如果存在重复值,则该答案无效:

public static <K, V extends Comparable<V>> Map<K, V> sortMapByValues(final Map<K, V> map) {
    Comparator<K> valueComparator =  new Comparator<K>() {
        public int compare(K k1, K k2) {
            final V v1 = map.get(k1);
            final V v2 = map.get(k2);

            /* Not sure how to handle nulls ... */
            if (v1 == null) {
                return (v2 == null) ? 0 : 1;
            }

            int compare = v2.compareTo(v1);
            if (compare != 0)
            {
                return compare;
            }
            else
            {
                Integer h1 = k1.hashCode();
                Integer h2 = k2.hashCode();
                return h2.compareTo(h1);
            }
        }
    };
    Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator);
    sortedByValues.putAll(map);
    return sortedByValues;
}

注意,如何处理空值还很难说。

这种方法的一个重要优点是它实际上返回了一个Map,这与这里提供的其他解决方案不同。