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


1634

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

由于值不是唯一的,因此我发现自己将转换keySetarray,然后使用自定义比较器数组进行排序,该自定义比较器对与键关联的值进行排序。

有没有更简单的方法?


24
映射不是要排序,而是可以快速访问。对象相等值打破了地图的约束。使用条目集,喜欢List<Map.Entry<...>> list =new LinkedList(map.entrySet())Collections.sort ....这种方式。
汉尼斯,2014年

1
当我们尝试使用Java中的Counter(Map <Object,Integer>)时,可能会出现这种情况。因此,按出现次数排序将是一种常见的操作。像Python这样的语言具有内置的Counter数据结构。对于Java实现的一种替代方法,这里是一个例子
demongolem

6
排序后的地图有很多用例,这就是为什么在jdk中有TreeMap和ConcurrentSkipListMap的原因。
alobodzk


1
TreeMap和ConcurrentSkipListMap按键排序。问题是关于按值排序。
彼得

Answers:


899

这是通用的版本:

public class MapUtil {
    public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
        List<Entry<K, V>> list = new ArrayList<>(map.entrySet());
        list.sort(Entry.comparingByValue());

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

        return result;
    }
}

10
很高兴这会有所帮助。John,LinkedHashMap对解决方案很重要,因为它提供了可预测的迭代顺序。
Carter Page

3
@ buzz3791是的。任何排序算法都将是这种情况。在排序期间更改结构中节点的值会产生不可预测的(几乎总是不好的)结果。
Carter Page

3
@Sheagorath我在Android中尝试过,它也可以工作。考虑到您使用的是Java 6版本,这不是特定于平台的问题。您是否在值对象中正确实现了Comparable
saiyancoder 2014年

6
Java 8版本不应该使用forEachOrdered而不是forEach,因为forEach状态文档:“此操作的行为明确地是不确定的。”?

1
完全撕开了它,但在评论中将@CarterPage归功于它(无论如何它将在开源项目中)。非常感谢。
弥敦道海滩

419

重要的提示:

该代码可以以多种方式破坏。如果您打算使用提供的代码,请务必阅读注释,并注意其中的含义。例如,无法再通过键检索值。(get总是返回null。)


似乎比上述所有内容都容易。使用TreeMap如下:

public class Testing {
    public static void main(String[] args) {
        HashMap<String, Double> map = new HashMap<String, Double>();
        ValueComparator bvc = new ValueComparator(map);
        TreeMap<String, Double> sorted_map = new TreeMap<String, Double>(bvc);

        map.put("A", 99.5);
        map.put("B", 67.4);
        map.put("C", 67.4);
        map.put("D", 67.3);

        System.out.println("unsorted map: " + map);
        sorted_map.putAll(map);
        System.out.println("results: " + sorted_map);
    }
}

class ValueComparator implements Comparator<String> {
    Map<String, Double> base;

    public ValueComparator(Map<String, Double> base) {
        this.base = base;
    }

    // Note: this comparator imposes orderings that are inconsistent with
    // equals.
    public int compare(String a, String b) {
        if (base.get(a) >= base.get(b)) {
            return -1;
        } else {
            return 1;
        } // returning 0 would merge keys
    }
}

输出:

unsorted map: {D=67.3, A=99.5, B=67.4, C=67.4}
results: {D=67.3, B=67.4, C=67.4, A=99.5}

18
再也没有了(stackoverflow.com/questions/109383/…)。另外,为什么要对Double进行强制转换?难道不是return ((Comparable)base.get(a).compareTo(((Comparable)base.get(b)))吗?
斯蒂芬

12
@Stephen:否。在这种情况下,将删除所有按值相等的键(等于和按引用进行比较之间的差异)。另外:即使此代码也有以下顺序问题 map.put("A","1d");map.put("B","1d");map.put("C",67d);map.put("D",99.5d);
2010年

43
用于树图的比较器与equals不一致(请参见sortMap javadox)。这意味着从树形图检索项目将不起作用。sorted_map.get(“ A”)将返回null。这意味着树形图的这种用法已损坏。
mR_fr0g 2010年

87
以防万一人们不清楚:如果您有多个键映射到相同的值,则此解决方案可能无法满足您的期望-排序后的结果中只会出现其中一个键。
Maxy-B

63
Louis Wasserman(是,Google Guava的其中一名成员)实际上非常不喜欢这个答案:“即使您看起来很有趣,它也会以几种真正令人困惑的方式打破。如果背景图发生变化,它将被打破。如果有多个键如果将get映射到相同的值,它将中断。如果调用get而不是位于备份映射中的键,它将中断。如果您执行任何可能导致查找不在键中的键的操作,则它将中断。地图-一个Map.equals调用,containsKey以及其他任何内容-都会被真正奇怪的堆栈跟踪破坏。” plus.google.com/102216152814616302326/posts/bEQLDK712MJ
haylem 2012年

339

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

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));

或打印到System.out

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

很好,但是parallelStream()在这种情况下如何使用?
杰2014年

11
它可以并行工作,但是,您可能会发现合并地图以合并部分结果的成本太高,并且并行版本的性能可能不如您希望的那样。但是它确实可以工作并产生正确的答案。
Brian Goetz 2014年

感谢您的有用建议。这正是我在想的,尽管它取决于您使用哪种类型的键以及如此多的参数...重要的是“它确实可以工作并产生正确的答案”。
Benj 2014年

2
您不必在top10示例中使用compareByValue吗?
狮子座

1
@Benj在提取前10名方面将起作用,但是将不再对生成的地图进行排序。
OrangeDog

211

三个1行答案...

我会用Google Collections Guava做到这一点-如果您的价值观是,Comparable那么您可以使用

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

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

如果它们不具有可比性,那么您需要按照以下步骤进行操作

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

这些可以应用于TreeMap(作为Ordering扩展Comparator),或经过某种排序后LinkedHashMap

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

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

= 将自然顺序应用于键所映射的值,并将其与键的自然顺序进行复合

请注意,如果您的键比较为0,这仍然不起作用,但是对于大多数comparable项目来说应该足够了(如hashCodeequals并且compareTo通常是同步的...)

请参阅Ordering.onResultOf()Functions.forMap()

实作

因此,现在我们有了一个可以满足我们需要的比较器,我们需要从中获取结果。

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

现在,这很可能会起作用,但是:

  1. 需要完成完整的地图
  2. 不要在上面尝试比较器TreeMap; 在插入之前没有值的情况下尝试比较插入的键是没有意义的,即,它将很快中断

第一点对我来说有点麻烦了;谷歌收藏集非常懒惰(这很好:您可以立即执行几乎所有操作;当您开始使用结果时,实际工作就完成了),这需要复制整个地图!

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

不过不要担心。如果您沉迷于以这种方式对“实时”地图进行排序,则可以通过以下类似的疯狂方法来解决上述两个问题之一:

注意:这种情况在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进行排序。但在此之前,我们检查哈希图以查看该密钥实际上不是重复项。此外,我们创建的比较器还将包含键,以便重复值不会删除非重复键(由于==比较)。这两个项目对于确保遵守地图合同至关重要。如果您认为自己不想这样做,则几乎可以将地图完全反转为Map<V,K>

构造函数需要被称为

 new ValueComparableMap(Ordering.natural());
 //or
 new ValueComparableMap(Ordering.from(comparator));

@Stephen,您好,您能举个示例如何使用Ordering吗?我查看了Ordering的源代码,但完全无法弄清.natural()。onResultOf(...)返回的结果!源代码是“ public <F> Ordering <F> onResultOf”,我什至不知道它是如何编译的!最重要的是,如何使用“ <F> Ordering <F>”对地图进行排序?是比较器还是其他东西?谢谢。
smallufo 2010年

Ordering简直就是有钱人Comparator。我尝试评论每个示例(每个示例下方的斜体)。“自然”表示对象是Comparable;就像apache common的ComparableComparator。onResultOf将函数应用于要比较的项目。因此,如果您有一个将1加到一个整数的函数,那么natural().onResultOf(add1Function).compare(1,2)最终会做2.compareTo(3)
Stephen

如果原始地图中存在重复的值,则ImmutableSortedMap.copyOf会引发IllegalArgumentException。
lbalazscs 2013年

@Ibalazscs是的-您应该可以使用ImmutableSetMultiMapImmutableListMultiMap包含重复变量的集合。
斯蒂芬,

1
谢谢您,我在一个项目中使用了您的解决方案。我认为这有一个问题:要像地图一样工作,它需要返回先前与该键相关联的值(如果存在),但是这样就永远不会做。我使用的解决方案是返回删除的值(如果存在)。
alex

185

http://www.programmersheaven.com/download/49349/download.aspx

private static <K, V> Map<K, V> sortByValue(Map<K, V> map) {
    List<Entry<K, V>> list = new LinkedList<>(map.entrySet());
    Collections.sort(list, new Comparator<Object>() {
        @SuppressWarnings("unchecked")
        public int compare(Object o1, Object o2) {
            return ((Comparable<V>) ((Map.Entry<K, V>) (o1)).getValue()).compareTo(((Map.Entry<K, V>) (o2)).getValue());
        }
    });

    Map<K, V> result = new LinkedHashMap<>();
    for (Iterator<Entry<K, V>> it = list.iterator(); it.hasNext();) {
        Map.Entry<K, V> entry = (Map.Entry<K, V>) it.next();
        result.put(entry.getKey(), entry.getValue());
    }

    return result;
}

16
要排序的列表是“新的LinkedList”?啧啧。值得庆幸的是,Collections.sort()首先将列表转储到数组,以避免恰好避免这种错误(但仍然,将ArrayList转储到数组应该比对LinkedList进行处理更快。)
Dimitris Andreou'4

无法从Iterator转换为TernaryTree.Iterator
lisak 2011年

4
@ gg.kaspersky我并不是说“对LinkedList进行排序很不好”,但是无论排序如何,LinkedList本身在这里都是不好的选择。 很多更好地使用ArrayList,而对于加分,它的大小正好map.size()。另请参见code.google.com/p/memory-measurer/wiki/…ArrayList中 每个元素的平均成本:5个字节LinkedList中每个元素的平均成本:24个字节。对于大小正确的ArrayList,平均开销为4个字节。也就是说,LinkedList的需要SIX倍的ArrayList需要的内存量。这只是膨胀
季米特里斯ANDREOU

2
使用上述值已按升序排序。如何降序排序?
2015年

1
替换o1和o2以降序排列。
Soheil

68

使用Java 8,您可以使用流api以明显更少的冗长方式进行操作:

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

如何以相反的顺序对其进行排序?
Vlad Holubiev 2014年

6
找到了解决办法-Collections.reverseOrder(comparing(Entry::getValue))
弗拉德Holubiev

1
我想我在那里看到一个错字-不应将“ toMap”称为“ Collectors.toMap()”吗?
杰克·斯托克斯

1
@JakeStokes或使用静态导入:-)
assylias

6
更好的以相反的顺序对条目值进行排序的方法是:Entry.comparingByValue(Comparator.reverseOrder())
Gediminas Rimsa

31

对键进行排序要求比较器为每个比较查找每个值。更具可扩展性的解决方案将直接使用entrySet,因为此后该值将立即可用于每个比较(尽管我尚未通过数字对其进行备份)。

这是这种事情的通用版本:

public static <K, V extends Comparable<? super V>> List<K> getKeysSortedByValue(Map<K, V> map) {
    final int size = map.size();
    final List<Map.Entry<K, V>> list = new ArrayList<Map.Entry<K, V>>(size);
    list.addAll(map.entrySet());
    final ValueComparator<V> cmp = new ValueComparator<V>();
    Collections.sort(list, cmp);
    final List<K> keys = new ArrayList<K>(size);
    for (int i = 0; i < size; i++) {
        keys.set(i, list.get(i).getKey());
    }
    return keys;
}

private static final class ValueComparator<V extends Comparable<? super V>>
                                     implements Comparator<Map.Entry<?, V>> {
    public int compare(Map.Entry<?, V> o1, Map.Entry<?, V> o2) {
        return o1.getValue().compareTo(o2.getValue());
    }
}

对于上述解决方案,有一些方法可以减少内存旋转。例如,创建的第一个ArrayList可以重新用作返回值;这将需要抑制一些泛型警告,但是对于可重用的库代码可能是值得的。同样,不必在每次调用时都重新分配比较器。

这是一个效率更高的版本,尽管吸引力较小:

public static <K, V extends Comparable<? super V>> List<K> getKeysSortedByValue2(Map<K, V> map) {
    final int size = map.size();
    final List reusedList = new ArrayList(size);
    final List<Map.Entry<K, V>> meView = reusedList;
    meView.addAll(map.entrySet());
    Collections.sort(meView, SINGLE);
    final List<K> keyView = reusedList;
    for (int i = 0; i < size; i++) {
        keyView.set(i, meView.get(i).getKey());
    }
    return keyView;
}

private static final Comparator SINGLE = new ValueComparator();

最后,如果您需要连续访问排序后的信息(而不仅仅是偶尔对它进行排序),则可以使用其他多图。让我知道您是否需要更多详细信息...


如果返回List <Map.Entry <K,V >>,则第二个版本可能更简洁。这也使迭代和获取键和值变得更加容易,而不必对地图进行大量额外操作。所有这些都假定您可以接受此代码对线程不安全的操作。如果在多线程环境中共享后勤图或排序列表,则所有选择都将关闭。
Mike Miller

26

commons-collections库包含一个名为TreeBidiMap的解决方案。或者,您可以查看Google Collections API。它具有TreeMultimap您可以使用。

而且,如果您不想使用这些框架,它们都带有源代码。


您不必使用commons-collection。Java带有自己的java.util.TreeMap。
yoliho

2
是的,但是在对mapentries 的部分进行排序时,TreeMap的灵活性远远不足。
p3t0r

9
BidiMap的麻烦在于,它在键和值之间添加了1:1关系约束,以使关系可逆(即,键和值都必须是唯一的)。这意味着您不能使用它来存储像单词计数对象之类的东西,因为许多单词将具有相同的计数。
道格

26

我已经看了给出的答案,但是其中许多答案比所需的更为复杂,或者当多个键具有相同的值时删除映射元素。

这是我认为更合适的解决方案:

public static <K, V extends Comparable<V>> Map<K, V> sortByValues(final Map<K, V> map) {
    Comparator<K> valueComparator =  new Comparator<K>() {
        public int compare(K k1, K k2) {
            int compare = map.get(k2).compareTo(map.get(k1));
            if (compare == 0) return 1;
            else return compare;
        }
    };
    Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator);
    sortedByValues.putAll(map);
    return sortedByValues;
}

请注意,映射是从最高值到最低值排序的。


6
问题:如果您以后想使用返回的映射,例如检查它是否包含某个元素,由于您的自定义比较器,您总是会得到false!一种可能的解决方案:将最后一行替换为:return new LinkedHashMap <K,V>(sortedByValues);
Erel Segal-Halevi

这对我来说是一个干净的解决方案,除了@ErelSegalHalevi指出的事实之外,因为您指定了比较器,因此无法检查Map中是否存在值。map.put(“ 1”,“一个”); map.put(“ 2”,“ Two”); map.put(“ 3”,“三个”); map.put(“ 4”,“四个”); map.put(“ 5”,“五个”); 如果您在sortByValues()函数中返回新对象,例如return new TreeMap <K,V>(sortedByValues); map.containsKey(“ 1”)将始终返回false。解决问题。由于ABHI
ABHI

与user157196和Carter Page的答案几乎相同。Carter Page包含LinkedHashMap修复程序
Kirby

解决方案的第四行应为int compare = map.get(k1).compareTo(map.get(k2)); 如果您需要升序
www.Decompiler.com 2014年

19

要使用Java 8中的新功能来实现此目的,请执行以下操作:

import static java.util.Map.Entry.comparingByValue;
import static java.util.stream.Collectors.toList;

<K, V> List<Entry<K, V>> sort(Map<K, V> map, Comparator<? super V> comparator) {
    return map.entrySet().stream().sorted(comparingByValue(comparator)).collect(toList());
}

使用给定的比较器,这些条目按其值排序。另外,如果您的值可以相互比较,则不需要显式比较器:

<K, V extends Comparable<? super V>> List<Entry<K, V>> sort(Map<K, V> map) {
    return map.entrySet().stream().sorted(comparingByValue()).collect(toList());
}

返回的列表是调用此方法时给定映射的快照,因此两者都不会反映对另一个映射的后续更改。要获得地图的实时迭代视图:

<K, V extends Comparable<? super V>> Iterable<Entry<K, V>> sort(Map<K, V> map) {
    return () -> map.entrySet().stream().sorted(comparingByValue()).iterator();
}

每次迭代时,返回的iterable都会为给定的映射创建一个新的快照,因此,除非进行并发修改,否则它将始终反映该映射的当前状态。


这将返回条目列表,而不是按值排序的映射。其他返回地图的版本:stackoverflow.com/a/22132422/829571
assylias 2014年

17

创建自定义的比较器,并在创建新的TreeMap对象时使用它。

class MyComparator implements Comparator<Object> {

    Map<String, Integer> map;

    public MyComparator(Map<String, Integer> map) {
        this.map = map;
    }

    public int compare(Object o1, Object o2) {

        if (map.get(o2) == map.get(o1))
            return 1;
        else
            return ((Integer) map.get(o2)).compareTo((Integer)     
                                                            map.get(o1));

    }
}

在您的主要功能中使用以下代码

    Map<String, Integer> lMap = new HashMap<String, Integer>();
    lMap.put("A", 35);
    lMap.put("B", 75);
    lMap.put("C", 50);
    lMap.put("D", 50);

    MyComparator comparator = new MyComparator(lMap);

    Map<String, Integer> newMap = new TreeMap<String, Integer>(comparator);
    newMap.putAll(lMap);
    System.out.println(newMap);

输出:

{B=75, D=50, C=50, A=35}

在值相等的情况下,我更改了“ return 1”行以比较键:“ return((String)o1).compareTo((String o2);“]
gjgjgj

14

尽管我同意对地图进行排序的持续需求可能是一种气味,但我认为以下代码是在不使用其他数据结构的情况下最简单的方法。

public class MapUtilities {

public static <K, V extends Comparable<V>> List<Entry<K, V>> sortByValue(Map<K, V> map) {
    List<Entry<K, V>> entries = new ArrayList<Entry<K, V>>(map.entrySet());
    Collections.sort(entries, new ByValue<K, V>());
    return entries;
}

private static class ByValue<K, V extends Comparable<V>> implements Comparator<Entry<K, V>> {
    public int compare(Entry<K, V> o1, Entry<K, V> o2) {
        return o1.getValue().compareTo(o2.getValue());
    }
}

}

这是一个令人尴尬的不完整的单元测试:

public class MapUtilitiesTest extends TestCase {
public void testSorting() {
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("One", 1);
    map.put("Two", 2);
    map.put("Three", 3);

    List<Map.Entry<String, Integer>> sorted = MapUtilities.sortByValue(map);
    assertEquals("First", "One", sorted.get(0).getKey());
    assertEquals("Second", "Two", sorted.get(1).getKey());
    assertEquals("Third", "Three", sorted.get(2).getKey());
}

}

结果是Map.Entry对象的排序列表,从中可以获取键和值。


与创建具有几乎相同效果的Map <V,List <K >>对象相比,此方法更加容易和直观。这些值实际上不应该是Map对象中的键,您真正想要的是在这种情况下的列表,恕我直言。
Jeff Wu

此解决方案不适用于很多值,它与我的计数(与每个键相关的值)搞砸了
Sam Levin 2012年

1
那很奇怪。你能详细说明吗?您的输出是什么,您期望的输出是什么?
Lyudmil 2012年

12

使用通用比较器,例如:

final class MapValueComparator<K,V extends Comparable<V>> implements Comparator<K> {

    private Map<K,V> map;

    private MapValueComparator() {
        super();
    }

    public MapValueComparator(Map<K,V> map) {
        this();
        this.map = map;
    }

    public int compare(K o1, K o2) {
        return map.get(o1).compareTo(map.get(o2));
    }
}

11

当您有两个相等的项目时,投票否定的答案无效。TreeMap会保留相等的值。

范例:未分类的地图

键/值:D / 67.3
键/值:A / 99.5
键/值:B / 67.4
键/值:C / 67.5
键/值:E / 99.5

结果

键/值:A / 99.5
键/值:C / 67.5
键/值:B / 67.4
键/值:D / 67.3

所以省掉了E !!

对我来说,调整比较器效果很好,如果不相等,则不返回0而是返回-1。

在示例中:

class ValueComparator实现Comparator {

地图库;public ValueComparator(Map base){this.base = base; }

public int compare(Object a,Object b){

if((Double)base.get(a) < (Double)base.get(b)) {
  return 1;
} else if((Double)base.get(a) == (Double)base.get(b)) {
  return -1;
} else {
  return -1;
}

}}

现在它返回:

未分类的地图:

键/值:D / 67.3
键/值:A / 99.5
键/值:B / 67.4
键/值:C / 67.5
键/值:E / 99.5

结果:

键/值:A / 99.5
键/值:E / 99.5
键/值:C / 67.5
键/值:B / 67.4
键/值:D / 67.3

作为对Aliens(2011年11月22日)的回应:我正在将此解决方案用于整数ID和名称的映射,但是想法是相同的,因此上面的代码可能不正确(我将在测试中编写它并提供正确的代码),这是基于上述解决方案进行Map排序的代码:

package nl.iamit.util;

import java.util.Comparator;
import java.util.Map;

public class Comparators {


    public static class MapIntegerStringComparator implements Comparator {

        Map<Integer, String> base;

        public MapIntegerStringComparator(Map<Integer, String> base) {
            this.base = base;
        }

        public int compare(Object a, Object b) {

            int compare = ((String) base.get(a))
                    .compareTo((String) base.get(b));
            if (compare == 0) {
                return -1;
            }
            return compare;
        }
    }


}

这是测试类(我刚刚测试过它,它适用于整数,字符串映射:

package test.nl.iamit.util;

import java.util.HashMap;
import java.util.TreeMap;
import nl.iamit.util.Comparators;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;

public class TestComparators {


    @Test
    public void testMapIntegerStringComparator(){
        HashMap<Integer, String> unSoretedMap = new HashMap<Integer, String>();
        Comparators.MapIntegerStringComparator bvc = new Comparators.MapIntegerStringComparator(
                unSoretedMap);
        TreeMap<Integer, String> sorted_map = new TreeMap<Integer, String>(bvc);
        //the testdata:
        unSoretedMap.put(new Integer(1), "E");
        unSoretedMap.put(new Integer(2), "A");
        unSoretedMap.put(new Integer(3), "E");
        unSoretedMap.put(new Integer(4), "B");
        unSoretedMap.put(new Integer(5), "F");

        sorted_map.putAll(unSoretedMap);

        Object[] targetKeys={new Integer(2),new Integer(4),new Integer(3),new Integer(1),new Integer(5) };
        Object[] currecntKeys=sorted_map.keySet().toArray();

        assertArrayEquals(targetKeys,currecntKeys);
    }
}

这是地图比较器的代码:

public static class MapStringDoubleComparator implements Comparator {

    Map<String, Double> base;

    public MapStringDoubleComparator(Map<String, Double> base) {
        this.base = base;
    }

    //note if you want decending in stead of ascending, turn around 1 and -1
    public int compare(Object a, Object b) {
        if ((Double) base.get(a) == (Double) base.get(b)) {
            return 0;
        } else if((Double) base.get(a) < (Double) base.get(b)) {
            return -1;
        }else{
            return 1;
        }
    }
}

这是为此的测试用例:

@Test
public void testMapStringDoubleComparator(){
    HashMap<String, Double> unSoretedMap = new HashMap<String, Double>();
    Comparators.MapStringDoubleComparator bvc = new Comparators.MapStringDoubleComparator(
            unSoretedMap);
    TreeMap<String, Double> sorted_map = new TreeMap<String, Double>(bvc);
    //the testdata:
    unSoretedMap.put("D",new Double(67.3));
    unSoretedMap.put("A",new Double(99.5));
    unSoretedMap.put("B",new Double(67.4));
    unSoretedMap.put("C",new Double(67.5));
    unSoretedMap.put("E",new Double(99.5));

    sorted_map.putAll(unSoretedMap);

    Object[] targetKeys={"D","B","C","E","A"};
    Object[] currecntKeys=sorted_map.keySet().toArray();

    assertArrayEquals(targetKeys,currecntKeys);
}

当然,您可以使它更加通用,但我只需要1种情况(地图)


您是对的,起初我提供的代码中有一些错误!希望我最近的编辑对您有所帮助。
michel.iamit 2011年

9

建议不要使用Collections.sort,而不要使用Arrays.sort。其实Collections.sort是这样的:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    Object[] a = list.toArray();
    Arrays.sort(a);
    ListIterator<T> i = list.listIterator();
    for (int j=0; j<a.length; j++) {
        i.next();
        i.set((T)a[j]);
    }
}

它只调用toArray列表,然后使用Arrays.sort。这样,所有映射条目将被复制3次:一次从映射到临时列表(可以是LinkedList或ArrayList),然后到临时数组,最后到新映射。

我的解决方案省略了这一步,因为它不会创建不必要的LinkedList。这是通用友好且性能最佳的代码:

public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) 
{
    @SuppressWarnings("unchecked")
    Map.Entry<K,V>[] array = map.entrySet().toArray(new Map.Entry[map.size()]);

    Arrays.sort(array, new Comparator<Map.Entry<K, V>>() 
    {
        public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) 
        {
            return e1.getValue().compareTo(e2.getValue());
        }
    });

    Map<K, V> result = new LinkedHashMap<K, V>();
    for (Map.Entry<K, V> entry : array)
        result.put(entry.getKey(), entry.getValue());

    return result;
}

8

这是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;
}

请注意,如何处理null几乎是悬而未决的。

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


这是不正确的,如果有重复的值,我的方法将起作用。我已经将其用于具有超过100个键的地图,这些键的值为“ 1”。
安东尼

8

最佳方法

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

public class OrderByValue {

  public static void main(String a[]){
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("java", 20);
    map.put("C++", 45);
    map.put("Unix", 67);
    map.put("MAC", 26);
    map.put("Why this kolavari", 93);
    Set<Entry<String, Integer>> set = map.entrySet();
    List<Entry<String, Integer>> list = new ArrayList<Entry<String, Integer>>(set);
    Collections.sort( list, new Comparator<Map.Entry<String, Integer>>()
    {
        public int compare( Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2 )
        {
            return (o1.getValue()).compareTo( o2.getValue() );//Ascending order
            //return (o2.getValue()).compareTo( o1.getValue() );//Descending order
        }
    } );
    for(Map.Entry<String, Integer> entry:list){
        System.out.println(entry.getKey()+" ==== "+entry.getValue());
    }
  }}

输出量

java ==== 20

MAC ==== 26

C++ ==== 45

Unix ==== 67

Why this kolavari ==== 93

7

主要问题。如果您使用第一个答案(Google将您带到这里),请更改比较器以添加一个equal子句,否则无法通过key从sorted_map获取值:

public int compare(String a, String b) {
        if (base.get(a) > base.get(b)) {
            return 1;
        } else if (base.get(a) < base.get(b)){
            return -1;
        } 

        return 0;
        // returning 0 would merge keys
    }

现在,当您添加两个具有相等值的条目时,它们将被合并,只有在您确定对象相同(相等)的情况下,才应返回0
Masood_mj 2013年

7

这个问题已经有很多答案,但是没有一个提供我想要的东西,一个地图实现返回按相关值排序的键和条目,并在映射中修改键和值时保持此属性。两个其他 问题询问此特别。

我编写了一个通用的友好示例来解决此用例。此实现未遵循Map接口的所有协定,例如,反映从原始对象的keySet()和entrySet()返回的集合中的值更改和删除。我觉得这样的解决方案太大了,无法包含在Stack Overflow答案中。如果我设法创建一个更完整的实现,也许我会将其发布到Github,然后链接到此答案的更新版本中。

import java.util.*;

/**
 * A map where {@link #keySet()} and {@link #entrySet()} return sets ordered
 * by associated values based on the the comparator provided at construction
 * time. The order of two or more keys with identical values is not defined.
 * <p>
 * Several contracts of the Map interface are not satisfied by this minimal
 * implementation.
 */
public class ValueSortedMap<K, V> extends HashMap<K, V> {
    protected Map<V, Collection<K>> valueToKeysMap;

    // uses natural order of value object, if any
    public ValueSortedMap() {
        this((Comparator<? super V>) null);
    }

    public ValueSortedMap(Comparator<? super V> valueComparator) {
        this.valueToKeysMap = new TreeMap<V, Collection<K>>(valueComparator);
    }

    public boolean containsValue(Object o) {
        return valueToKeysMap.containsKey(o);
    }

    public V put(K k, V v) {
        V oldV = null;
        if (containsKey(k)) {
            oldV = get(k);
            valueToKeysMap.get(oldV).remove(k);
        }
        super.put(k, v);
        if (!valueToKeysMap.containsKey(v)) {
            Collection<K> keys = new ArrayList<K>();
            keys.add(k);
            valueToKeysMap.put(v, keys);
        } else {
            valueToKeysMap.get(v).add(k);
        }
        return oldV;
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }

    public V remove(Object k) {
        V oldV = null;
        if (containsKey(k)) {
            oldV = get(k);
            super.remove(k);
            valueToKeysMap.get(oldV).remove(k);
        }
        return oldV;
    }

    public void clear() {
        super.clear();
        valueToKeysMap.clear();
    }

    public Set<K> keySet() {
        LinkedHashSet<K> ret = new LinkedHashSet<K>(size());
        for (V v : valueToKeysMap.keySet()) {
            Collection<K> keys = valueToKeysMap.get(v);
            ret.addAll(keys);
        }
        return ret;
    }

    public Set<Map.Entry<K, V>> entrySet() {
        LinkedHashSet<Map.Entry<K, V>> ret = new LinkedHashSet<Map.Entry<K, V>>(size());
        for (Collection<K> keys : valueToKeysMap.values()) {
            for (final K k : keys) {
                final V v = get(k);
                ret.add(new Map.Entry<K,V>() {
                    public K getKey() {
                        return k;
                    }

                    public V getValue() {
                        return v;
                    }

                    public V setValue(V v) {
                        throw new UnsupportedOperationException();
                    }
                });
            }
        }
        return ret;
    }
}

如果不允许使用Comparable和Comparator,该怎么办?
Ved Prakash

不知道我是否了解您的用例,也许您可​​以详细说明。如果您希望用作值的对象不是可比较的,则需要将其转换为对象。
大卫·布莱克曼

6

迟到。

随着Java-8的出现,我们可以以非常简单/简洁的方式将流用于数据处理。您可以使用流按值对映射条目进行排序,并创建一个LinkedHashMap来保留插入顺序的迭代。

例如:

LinkedHashMap sortedByValueMap = map.entrySet().stream()
                .sorted(comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey))     //first sorting by Value, then sorting by Key(entries with same value)
                .collect(LinkedHashMap::new,(map,entry) -> map.put(entry.getKey(),entry.getValue()),LinkedHashMap::putAll);

对于逆序订购,请更换:

comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)

comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey).reversed()

感谢您的此评论版本。一个问题:使用Entry.comparingByValue()(或上面stackoverflow.com/a/22132422/1480587的 assylias回答)或comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)您使用的有什么区别?我了解您也可以比较键(如果值相同),对吗?我注意到排序使元素的顺序具有相同的值-如果键碰巧之前已经排序过,那么按键排序是否必要?
Peter T.

6

给定地图

   Map<String, Integer> wordCounts = new HashMap<>();
    wordCounts.put("USA", 100);
    wordCounts.put("jobs", 200);
    wordCounts.put("software", 50);
    wordCounts.put("technology", 70);
    wordCounts.put("opportunity", 200);

根据值升序对地图排序

Map<String,Integer>  sortedMap =  wordCounts.entrySet().
                                                stream().
                                                sorted(Map.Entry.comparingByValue()).
        collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
    System.out.println(sortedMap);

根据值按降序对地图排序

Map<String,Integer>  sortedMapReverseOrder =  wordCounts.entrySet().
            stream().
            sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).
            collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
    System.out.println(sortedMapReverseOrder);

输出:

{软件= 50,技术= 70,美国= 100,工作= 200,机会= 200}

{职位= 200,机会= 200,美国= 100,技术= 70,软件= 50}


我想map-reduce确实“减少了”它。
ha9u63ar

感谢@ ha9u63ar
Arpan Saini

它可以工作,但我不了解HashMap中元素的顺序如何发挥作用?
阿里·图

5

根据上下文的不同,使用java.util.LinkedHashMap<T>哪种记忆来将项目放置到地图中的顺序。否则,如果您需要根据自然顺序对值进行排序,我建议您维护一个单独的List,该列表可以通过进行排序Collections.sort()


我不知道为什么是-1,到目前为止,LinkedHashMap可能对我来说是最好的解决方案,我只是想弄清楚扔掉并创建一个新的LinkedHashMap会有多昂贵。
NobleUplift '16

5

由于TreeMap <>不适用于相等的值,因此我使用了以下方法:

private <K, V extends Comparable<? super V>> List<Entry<K, V>> sort(Map<K, V> map)     {
    List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>(map.entrySet());
    Collections.sort(list, new Comparator<Map.Entry<K, V>>() {
        public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
            return o1.getValue().compareTo(o2.getValue());
        }
    });

    return list;
}

您可能希望将列表放入LinkedHashMap中,但是如果仅要立即对其进行迭代,那就多余了...


是的,但是您的比较器无法处理等于值的情况
Sebastien Lorber

5

这太复杂了。地图不应执行按值对地图进行排序的工作。最简单的方法是创建自己的类,使其适合您的要求。

在下面的示例中,您应该在*所在的位置添加TreeMap一个比较器。但是通过Java API,它仅向比较器提供键,而不是值。此处陈述的所有示例均基于2张地图。一棵哈希和一棵新树。真奇怪

这个例子:

Map<Driver driver, Float time> map = new TreeMap<Driver driver, Float time>(*);

因此,可以通过以下方式将地图更改为集合:

ResultComparator rc = new ResultComparator();
Set<Results> set = new TreeSet<Results>(rc);

您将创建课程 Results

public class Results {
    private Driver driver;
    private Float time;

    public Results(Driver driver, Float time) {
        this.driver = driver;
        this.time = time;
    }

    public Float getTime() {
        return time;
    }

    public void setTime(Float time) {
        this.time = time;
    }

    public Driver getDriver() {
        return driver;
    }

    public void setDriver (Driver driver) {
        this.driver = driver;
    }
}

和Comparator类:

public class ResultsComparator implements Comparator<Results> {
    public int compare(Results t, Results t1) {
        if (t.getTime() < t1.getTime()) {
            return 1;
        } else if (t.getTime() == t1.getTime()) {
            return 0;
        } else {
            return -1;
        }
    }
}

这样,您可以轻松添加更多依赖项。

最后,我将添加简单的迭代器:

Iterator it = set.iterator();
while (it.hasNext()) {
    Results r = (Results)it.next();
    System.out.println( r.getDriver().toString
        //or whatever that is related to Driver class -getName() getSurname()
        + " "
        + r.getTime()
        );
}

4

基于@devinmoore代码,这是一种使用泛型并支持升序和降序排序的地图排序方法。

/**
 * Sort a map by it's keys in ascending order. 
 *  
 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
 * @author Maxim Veksler
 */
public static <K, V> LinkedHashMap<K, V> sortMapByKey(final Map<K, V> map) {
    return sortMapByKey(map, SortingOrder.ASCENDING);
}

/**
 * Sort a map by it's values in ascending order.
 *  
 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
 * @author Maxim Veksler
 */
public static <K, V> LinkedHashMap<K, V> sortMapByValue(final Map<K, V> map) {
    return sortMapByValue(map, SortingOrder.ASCENDING);
}

/**
 * Sort a map by it's keys.
 *  
 * @param sortingOrder {@link SortingOrder} enum specifying requested sorting order. 
 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
 * @author Maxim Veksler
 */
public static <K, V> LinkedHashMap<K, V> sortMapByKey(final Map<K, V> map, final SortingOrder sortingOrder) {
    Comparator<Map.Entry<K, V>> comparator = new Comparator<Entry<K,V>>() {
        public int compare(Entry<K, V> o1, Entry<K, V> o2) {
            return comparableCompare(o1.getKey(), o2.getKey(), sortingOrder);
        }
    };

    return sortMap(map, comparator);
}

/**
 * Sort a map by it's values.
 *  
 * @param sortingOrder {@link SortingOrder} enum specifying requested sorting order. 
 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
 * @author Maxim Veksler
 */
public static <K, V> LinkedHashMap<K, V> sortMapByValue(final Map<K, V> map, final SortingOrder sortingOrder) {
    Comparator<Map.Entry<K, V>> comparator = new Comparator<Entry<K,V>>() {
        public int compare(Entry<K, V> o1, Entry<K, V> o2) {
            return comparableCompare(o1.getValue(), o2.getValue(), sortingOrder);
        }
    };

    return sortMap(map, comparator);
}

@SuppressWarnings("unchecked")
private static <T> int comparableCompare(T o1, T o2, SortingOrder sortingOrder) {
    int compare = ((Comparable<T>)o1).compareTo(o2);

    switch (sortingOrder) {
    case ASCENDING:
        return compare;
    case DESCENDING:
        return (-1) * compare;
    }

    return 0;
}

/**
 * Sort a map by supplied comparator logic.
 *  
 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
 * @author Maxim Veksler
 */
public static <K, V> LinkedHashMap<K, V> sortMap(final Map<K, V> map, final Comparator<Map.Entry<K, V>> comparator) {
    // Convert the map into a list of key,value pairs.
    List<Map.Entry<K, V>> mapEntries = new LinkedList<Map.Entry<K, V>>(map.entrySet());

    // Sort the converted list according to supplied comparator.
    Collections.sort(mapEntries, comparator);

    // Build a new ordered map, containing the same entries as the old map.  
    LinkedHashMap<K, V> result = new LinkedHashMap<K, V>(map.size() + (map.size() / 20));
    for(Map.Entry<K, V> entry : mapEntries) {
        // We iterate on the mapEntries list which is sorted by the comparator putting new entries into 
        // the targeted result which is a sorted map. 
        result.put(entry.getKey(), entry.getValue());
    }

    return result;
}

/**
 * Sorting order enum, specifying request result sort behavior.
 * @author Maxim Veksler
 *
 */
public static enum SortingOrder {
    /**
     * Resulting sort will be from smaller to biggest.
     */
    ASCENDING,
    /**
     * Resulting sort will be from biggest to smallest.
     */
    DESCENDING
}

再一次,也许更好的解决方案是仅使用自排序图,在这种情况下,请使用org.apache.commons.collections.bidimap.TreeBidiMap
Maxim Veksler 2009年

4

这是一个OO解决方案(即,不使用static方法):

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class SortableValueMap<K, V extends Comparable<V>>
  extends LinkedHashMap<K, V> {
  public SortableValueMap() { }

  public SortableValueMap( Map<K, V> map ) {
    super( map );
  }

  public void sortByValue() {
    List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>( entrySet() );

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

    clear();

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

  private static void print( String text, Map<String, Double> map ) {
    System.out.println( text );

    for( String key : map.keySet() ) {
      System.out.println( "key/value: " + key + "/" + map.get( key ) );
    }
  }

  public static void main( String[] args ) {
    SortableValueMap<String, Double> map =
      new SortableValueMap<String, Double>();

    map.put( "A", 67.5 );
    map.put( "B", 99.5 );
    map.put( "C", 82.4 );
    map.put( "D", 42.0 );

    print( "Unsorted map", map );
    map.sortByValue();
    print( "Sorted map", map );
  }
}

特此捐赠给公共领域。


4

Afaik最干净的方法是利用集合对价值地图进行排序:

Map<String, Long> map = new HashMap<String, Long>();
// populate with data to sort on Value
// use datastructure designed for sorting

Queue queue = new PriorityQueue( map.size(), new MapComparable() );
queue.addAll( map.entrySet() );

// get a sorted map
LinkedHashMap<String, Long> linkedMap = new LinkedHashMap<String, Long>();

for (Map.Entry<String, Long> entry; (entry = queue.poll())!=null;) {
    linkedMap.put(entry.getKey(), entry.getValue());
}

public static class MapComparable implements Comparator<Map.Entry<String, Long>>{

  public int compare(Entry<String, Long> e1, Entry<String, Long> e2) {
    return e1.getValue().compareTo(e2.getValue());
  }
}

4

为了进行排序的映射,其中包含重复的值对,可以进行一些简单的更改。在compare方法(ValueComparator类)中,当值相等时,不返回0,而是返回比较2个键的结果。键在映射中是不同的,因此您成功保留了重复的值(顺便说一下,这些值按键排序)。所以上面的例子可以这样修改:

    public int compare(Object a, Object b) {

        if((Double)base.get(a) < (Double)base.get(b)) {
          return 1;
        } else if((Double)base.get(a) == (Double)base.get(b)) {
          return ((String)a).compareTo((String)b);
        } else {
          return -1;
        }
      }
    }

4

当然,Stephen的解决方案确实很棒,但是对于那些无法使用番石榴的人来说:

这是我按值对地图排序的解决方案。此解决方案可以处理两个相同值等等的情况。

// If you want to sort a map by value, and if there can be twice the same value:

// here is your original map
Map<String,Integer> mapToSortByValue = new HashMap<String, Integer>();
mapToSortByValue.put("A", 3);
mapToSortByValue.put("B", 1);
mapToSortByValue.put("C", 3);
mapToSortByValue.put("D", 5);
mapToSortByValue.put("E", -1);
mapToSortByValue.put("F", 1000);
mapToSortByValue.put("G", 79);
mapToSortByValue.put("H", 15);

// Sort all the map entries by value
Set<Map.Entry<String,Integer>> set = new TreeSet<Map.Entry<String,Integer>>(
        new Comparator<Map.Entry<String,Integer>>(){
            @Override
            public int compare(Map.Entry<String,Integer> obj1, Map.Entry<String,Integer> obj2) {
                Integer val1 = obj1.getValue();
                Integer val2 = obj2.getValue();
                // DUPLICATE VALUE CASE
                // If the values are equals, we can't return 0 because the 2 entries would be considered
                // as equals and one of them would be deleted (because we use a set, no duplicate, remember!)
                int compareValues = val1.compareTo(val2);
                if ( compareValues == 0 ) {
                    String key1 = obj1.getKey();
                    String key2 = obj2.getKey();
                    int compareKeys = key1.compareTo(key2);
                    if ( compareKeys == 0 ) {
                        // what you return here will tell us if you keep REAL KEY-VALUE duplicates in your set
                        // if you want to, do whatever you want but do not return 0 (but don't break the comparator contract!)
                        return 0;
                    }
                    return compareKeys;
                }
                return compareValues;
            }
        }
);
set.addAll(mapToSortByValue.entrySet());


// OK NOW OUR SET IS SORTED COOL!!!!

// And there's nothing more to do: the entries are sorted by value!
for ( Map.Entry<String,Integer> entry : set ) {
    System.out.println("Set entries: " + entry.getKey() + " -> " + entry.getValue());
}




// But if you add them to an hashmap
Map<String,Integer> myMap = new HashMap<String,Integer>();
// When iterating over the set the order is still good in the println...
for ( Map.Entry<String,Integer> entry : set ) {
    System.out.println("Added to result map entries: " + entry.getKey() + " " + entry.getValue());
    myMap.put(entry.getKey(), entry.getValue());
}

// But once they are in the hashmap, the order is not kept!
for ( Integer value : myMap.values() ) {
    System.out.println("Result map values: " + value);
}
// Also this way doesn't work:
// Logic because the entryset is a hashset for hashmaps and not a treeset
// (and even if it was a treeset, it would be on the keys only)
for ( Map.Entry<String,Integer> entry : myMap.entrySet() ) {
    System.out.println("Result map entries: " + entry.getKey() + " -> " + entry.getValue());
}


// CONCLUSION:
// If you want to iterate on a map ordered by value, you need to remember:
// 1) Maps are only sorted by keys, so you can't sort them directly by value
// 2) So you simply CAN'T return a map to a sortMapByValue function
// 3) You can't reverse the keys and the values because you have duplicate values
//    This also means you can't neither use Guava/Commons bidirectionnal treemaps or stuff like that

// SOLUTIONS
// So you can:
// 1) only sort the values which is easy, but you loose the key/value link (since you have duplicate values)
// 2) sort the map entries, but don't forget to handle the duplicate value case (like i did)
// 3) if you really need to return a map, use a LinkedHashMap which keep the insertion order

执行人员:http : //www.ideone.com/dq3Lu

输出:

Set entries: E -> -1
Set entries: B -> 1
Set entries: A -> 3
Set entries: C -> 3
Set entries: D -> 5
Set entries: H -> 15
Set entries: G -> 79
Set entries: F -> 1000
Added to result map entries: E -1
Added to result map entries: B 1
Added to result map entries: A 3
Added to result map entries: C 3
Added to result map entries: D 5
Added to result map entries: H 15
Added to result map entries: G 79
Added to result map entries: F 1000
Result map values: 5
Result map values: -1
Result map values: 1000
Result map values: 79
Result map values: 3
Result map values: 1
Result map values: 3
Result map values: 15
Result map entries: D -> 5
Result map entries: E -> -1
Result map entries: F -> 1000
Result map entries: G -> 79
Result map entries: A -> 3
Result map entries: B -> 1
Result map entries: C -> 3
Result map entries: H -> 15

希望对大家有帮助


3

如果您有重复的键并且只有少量数据(<1000),并且您的代码对性能没有要求,则可以执行以下操作:

Map<String,Integer> tempMap=new HashMap<String,Integer>(inputUnsortedMap);
LinkedHashMap<String,Integer> sortedOutputMap=new LinkedHashMap<String,Integer>();

for(int i=0;i<inputUnsortedMap.size();i++){
    Map.Entry<String,Integer> maxEntry=null;
    Integer maxValue=-1;
    for(Map.Entry<String,Integer> entry:tempMap.entrySet()){
        if(entry.getValue()>maxValue){
            maxValue=entry.getValue();
            maxEntry=entry;
        }
    }
    tempMap.remove(maxEntry.getKey());
    sortedOutputMap.put(maxEntry.getKey(),maxEntry.getValue());
}

inputUnsortedMap是代码的输入。

迭代时,sortedOutputMap变量将按降序包含数据。要更改顺序,只需在if语句中将>更改为<。

不是最快的排序,但是可以在没有任何其他依赖项的情况下完成工作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.