按值对 HashMap 进行排序

165
我需要根据 HashMap 内存储的值来对其进行排序。该 HashMap 包含在电话中存储的联系人姓名。
同时,我需要键随着值的排序自动排序,或者可以说键和值是绑在一起的,因此值的任何更改都应反映在键中。
HashMap<Integer,String> map = new HashMap<Integer,String>();
map.put(1,"froyo");
map.put(2,"abby");
map.put(3,"denver");
map.put(4,"frost");
map.put(5,"daisy");

所需输出:

2,abby;
5,daisy;
3,denver;
4,frost;
1,froyo;
12个回答

189

对于一个用于排序Map的通用版本方法类似于:

private static <K extends Comparable<K>, V extends Comparable<V>> Map<K, V> sort(
        final Map<K, V> unsorted,
        final boolean order) {
    final var list = new LinkedList<>(unsorted.entrySet());

    list.sort((o1, o2) -> order
                          ? o1.getValue().compareTo(o2.getValue()) == 0
                            ? o1.getKey().compareTo(o2.getKey())
                            : o1.getValue().compareTo(o2.getValue())
                          : o2.getValue().compareTo(o1.getValue()) == 0
                            ? o2.getKey().compareTo(o1.getKey())
                            : o2.getValue().compareTo(o1.getValue()));
    return list.stream().collect(
            Collectors.toMap(
                    Entry::getKey, Entry::getValue, (a, b) -> b, LinkedHashMap::new
            )
    );
}
以下代码提供按值升序和降序排序的功能:
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class SortMapByValue
{
    public static final boolean ASC = true;
    public static final boolean DESC = false;

    public static void main(String[] args)
    {

        // Creating dummy unsorted map
        Map<String, Integer> unsortMap = new HashMap<String, Integer>();
        unsortMap.put("B", 55);
        unsortMap.put("A", 80);
        unsortMap.put("D", 20);
        unsortMap.put("C", 70);

        System.out.println("Before sorting......");
        printMap(unsortMap);

        System.out.println("After sorting ascending order......");
        Map<String, Integer> sortedMapAsc = sortByComparator(unsortMap, ASC);
        printMap(sortedMapAsc);
        
        
        System.out.println("After sorting descindeng order......");
        Map<String, Integer> sortedMapDesc = sortByComparator(unsortMap, DESC);
        printMap(sortedMapDesc);

    }

    private static Map<String, Integer> sortByComparator(Map<String, Integer> unsortMap, final boolean order)
    {

        List<Entry<String, Integer>> list = new LinkedList<Entry<String, Integer>>(unsortMap.entrySet());

        // Sorting the list based on values
        Collections.sort(list, new Comparator<Entry<String, Integer>>()
        {
            public int compare(Entry<String, Integer> o1,
                    Entry<String, Integer> o2)
            {
                if (order)
                {
                    return o1.getValue().compareTo(o2.getValue());
                }
                else
                {
                    return o2.getValue().compareTo(o1.getValue());

                }
            }
        });

        // Maintaining insertion order with the help of LinkedList
        Map<String, Integer> sortedMap = new LinkedHashMap<String, Integer>();
        for (Entry<String, Integer> entry : list)
        {
            sortedMap.put(entry.getKey(), entry.getValue());
        }

        return sortedMap;
    }

    public static void printMap(Map<String, Integer> map)
    {
        for (Entry<String, Integer> entry : map.entrySet())
        {
            System.out.println("Key : " + entry.getKey() + " Value : "+ entry.getValue());
        }
    }
}

使用较新的Java功能:

 import java.util.*;
 import java.util.Map.Entry;
 import java.util.stream.Collectors;
    
 public class SortMapByValue

 {
    private static final boolean ASC = true;
    private static final boolean DESC = false;
    public static void main(String[] args)
    {

        // Creating dummy unsorted map
        Map<String, Integer> unsortMap = new HashMap<>();
        unsortMap.put("B", 55);
        unsortMap.put("A", 20);
        unsortMap.put("D", 20);
        unsortMap.put("C", 70);

        System.out.println("Before sorting......");
        printMap(unsortMap);

        System.out.println("After sorting ascending order......");
        Map<String, Integer> sortedMapAsc = sortByValue(unsortMap, ASC);
        printMap(sortedMapAsc);


        System.out.println("After sorting descending order......");
        Map<String, Integer> sortedMapDesc = sortByValue(unsortMap, DESC);
        printMap(sortedMapDesc);
    }

    private static Map<String, Integer> sortByValue(Map<String, Integer> unsortMap, final boolean order)
    {
        List<Entry<String, Integer>> list = new LinkedList<>(unsortMap.entrySet());

        // Sorting the list based on values
        list.sort((o1, o2) -> order ? o1.getValue().compareTo(o2.getValue()) == 0
                ? o1.getKey().compareTo(o2.getKey())
                : o1.getValue().compareTo(o2.getValue()) : o2.getValue().compareTo(o1.getValue()) == 0
                ? o2.getKey().compareTo(o1.getKey())
                : o2.getValue().compareTo(o1.getValue()));
        return list.stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b, LinkedHashMap::new));

    }

    private static void printMap(Map<String, Integer> map)
    {
        map.forEach((key, value) -> System.out.println("Key : " + key + " Value : " + value));
    }
}

4
好的答案,使用集合工具类以适当的方式编写。 - Aditya
2
尝试解决我的问题后发现,您的比较器逻辑需要进行轻微修改,因为您没有检查空值,而地图值可能包含空元素。 - Aditya
如果我想按value的长度排序,例如它是一个字符串,该怎么办? - Srujan Barai
如果值相同,上述代码会按键排序吗? - Tushar Banne
@RaisAlam 如果我们的键不是单个字符,而是像AAA和AAB这样的单词,那么我们该如何按字母顺序排列呢? - givexa
显示剩余2条评论

120

在Java 8中:

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

1
这个排序可以反转吗?我需要的值是从大到小的。 - Aquarius Power
3
请参考 https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#reversed-- 中的 reversed 方法。您可以将其应用于由 comparingByValue() 返回的比较器。 - Vitalii Fedorenko
4
Entry.comparingByValue().reversed() 的返回值与 .sorted(...) 期望的参数不兼容 :(,我最终使用了一个反向循环 for 遍历 .entrySet().toArray(),也许可以通过强制转换来解决,我需要更多时间进行测试 :) - Aquarius Power
5
@AquariusPower,你不需要进行强制类型转换,只需向编译器提供一个关于泛型类型的提示:Entry.<Integer, String>comparingByValue().reversed()。这样做不会改变原意,我已经尽可能让翻译内容通俗易懂。 - Vitalii Fedorenko
12
这种编程方式正是我讨厌jdk8的原因。getKey,getValue,(e1,e2)->e1,::new...然后我们可以在篝火周围召唤“wooloh loh”,呼唤夜之灵魂... - hephestos
显示剩余5条评论

101

假设使用Java,你可以像这样对哈希表进行排序:

public LinkedHashMap<Integer, String> sortHashMapByValues(
        HashMap<Integer, String> passedMap) {
    List<Integer> mapKeys = new ArrayList<>(passedMap.keySet());
    List<String> mapValues = new ArrayList<>(passedMap.values());
    Collections.sort(mapValues);
    Collections.sort(mapKeys);

    LinkedHashMap<Integer, String> sortedMap =
        new LinkedHashMap<>();

    Iterator<String> valueIt = mapValues.iterator();
    while (valueIt.hasNext()) {
        String val = valueIt.next();
        Iterator<Integer> keyIt = mapKeys.iterator();

        while (keyIt.hasNext()) {
            Integer key = keyIt.next();
            String comp1 = passedMap.get(key);
            String comp2 = val;

            if (comp1.equals(comp2)) {
                keyIt.remove();
                sortedMap.put(key, val);
                break;
            }
        }
    }
    return sortedMap;
}

这只是一个启动示例。这种方法更有用,因为它对HashMap进行排序并保留重复值。


我认为collections.sort(mapvalues)不能解决问题。 - prof_jack
太棒了 :) 百分百工作 - user3402040
我们可以使用类似这样的东西吗?Arrays.sort(hm.values().toArray()); - Hengameh
29
请注意,由于需要在两个while循环中重复查找数值,所以给定的算法时间复杂度为O(n^2)。将Entry Set转换为List,然后根据比较器对List进行排序会是一种更有效的解决方案。 - picmate 涅
1
这种方法的主要问题是由于嵌套的 while 循环需要二次时间。有更好的答案,比如下面来自 Rais Alarm 的优秀答案。 - Vargan
显示剩余2条评论

40
map.entrySet().stream()
                .sorted((k1, k2) -> -k1.getValue().compareTo(k2.getValue()))
                .forEach(k -> System.out.println(k.getKey() + ": " + k.getValue()));

2
对我来说最易读且能胜任工作的解决方案,例如在按值排序的HashMap上运行。 - 1813222
请将以下与编程相关的内容从英文翻译成中文。只返回翻译后的文本:简洁明了;优雅易读。 - Woden
这会按照 map 的值的降序打印,我该如何强制它按升序排列? - Rohan
1
从k1.getValue()中去掉减号 @Rohan - Kishy Nivas

26
基本上是不行的。一个HashMap本质上是无序的。在排序中可能看到的任何模式都不应该依赖它们。
有一些已排序的映射,例如TreeMap,但它们传统上按键而非值进行排序。按值排序相对较少见,特别是因为多个键可以具有相同的值。
您能否提供更多关于您所要做的事情的背景?如果您只是存储数字(作为字符串)作为键,也许像TreeSet这样的SortedSet适合您?
或者,您可以存储两个单独的集合,封装在一个类中以同时更新它们?

1
例如,对出现在图像上的颜色进行排序。这必须要快,因为我们可能有最大整数个颜色。 - Rafael Sanches
@RafaelSanches:不清楚你评论的上下文是什么。在这种情况下,map 是什么?你可能需要提出一个新问题。 - Jon Skeet
1
我只是举例说明一种最有效的方式,可以按值对哈希映射进行排序。 - Rafael Sanches
我们可以使用类似这样的东西吗?Arrays.sort(hm.values().toArray()); - Hengameh
1
@VedPrakash:如果您使用总分作为键存储映射,那么您不应该期望能够存储两个具有相同总分的值。我建议您提出一个新问题,并更详细地描述您面临的问题。 - Jon Skeet
显示剩余8条评论

24
package com.naveen.hashmap;

import java.util.*;
import java.util.Map.Entry;

public class SortBasedonValues {

    /**
     * @param args
     */
    public static void main(String[] args) {

        HashMap<String, Integer> hm = new HashMap<String, Integer>();
        hm.put("Naveen", 2);
        hm.put("Santosh", 3);
        hm.put("Ravi", 4);
        hm.put("Pramod", 1);
        Set<Entry<String, Integer>> set = hm.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 o2.getValue().compareTo(o1.getValue());
            }
        });

        for (Entry<String, Integer> entry : list) {
            System.out.println(entry.getValue());

        }

    }
}

我尝试执行这段代码,使用import java.util.Map.Entry;可以正常运行。但是使用import java.util.*;却无法运行。根据我的了解,后者包含前者。那么为什么会出现错误呢? - NaveeNeo
简单而优雅 - Caveman

12

如果您只需要最终结果,可以使用临时TreeMap作为一种简单的解决方案:

TreeMap<String, Integer> sortedMap = new TreeMap<String, Integer>();
for (Map.Entry entry : map.entrySet()) {
    sortedMap.put((String) entry.getValue(), (Integer)entry.getKey());
}

这将以sortedMap的键作为排序后的字符串返回。


30
只有当所有整数值都是唯一的时候,这个方法才起作用——否则,你就会得到被覆盖的字符串。 - Kyzderp

5

我继承了TreeMap,并覆盖了entrySet()和values()方法。键和值需要可比较。

以下是代码:

public class ValueSortedMap<K extends Comparable, V extends Comparable> extends TreeMap<K, V> {

    @Override
    public Set<Entry<K, V>> entrySet() {
        Set<Entry<K, V>> originalEntries = super.entrySet();
        Set<Entry<K, V>> sortedEntry = new TreeSet<Entry<K, V>>(new Comparator<Entry<K, V>>() {
            @Override
            public int compare(Entry<K, V> entryA, Entry<K, V> entryB) {
                int compareTo = entryA.getValue().compareTo(entryB.getValue());
                if(compareTo == 0) {
                    compareTo = entryA.getKey().compareTo(entryB.getKey());
                }
                return compareTo;
            }
        });
        sortedEntry.addAll(originalEntries);
        return sortedEntry;
    }

    @Override
    public Collection<V> values() {
        Set<V> sortedValues = new TreeSet<>(new Comparator<V>(){
            @Override
            public int compare(V vA, V vB) {
                return vA.compareTo(vB);
            }
        });
        sortedValues.addAll(super.values());
        return sortedValues;
    }
}

单元测试:

public class ValueSortedMapTest {

    @Test
    public void basicTest() {
        Map<String, Integer> sortedMap = new ValueSortedMap<>();
        sortedMap.put("A",3);
        sortedMap.put("B",1);
        sortedMap.put("C",2);

        Assert.assertEquals("{B=1, C=2, A=3}", sortedMap.toString());
    }

    @Test
    public void repeatedValues() {
        Map<String, Double> sortedMap = new ValueSortedMap<>();
        sortedMap.put("D",67.3);
        sortedMap.put("A",99.5);
        sortedMap.put("B",67.4);
        sortedMap.put("C",67.4);

        Assert.assertEquals("{D=67.3, B=67.4, C=67.4, A=99.5}", sortedMap.toString());
    }

}

1
这不符合Map接口的规范。一个正确的实现entrySet()应该是:“返回此映射中所包含映射的Set视图。该集合由映射支持,因此对映射的更改会反映在集合中,反之亦然。”values()同理。 - Radiodef
太棒了,正是我在寻找的。 - Shashank

2

找到了一个解决方案,但不确定当地图尺寸很大时性能如何,适用于正常情况。

   /**
     * sort HashMap<String, CustomData> by value
     * CustomData needs to provide compareTo() for comparing CustomData
     * @param map
     */

    public void sortHashMapByValue(final HashMap<String, CustomData> map) {
        ArrayList<String> keys = new ArrayList<String>();
        keys.addAll(map.keySet());
        Collections.sort(keys, new Comparator<String>() {
            @Override
            public int compare(String lhs, String rhs) {
                CustomData val1 = map.get(lhs);
                CustomData val2 = map.get(rhs);
                if (val1 == null) {
                    return (val2 != null) ? 1 : 0;
                } else if (val1 != null) && (val2 != null)) {
                    return = val1.compareTo(val2);
                }
                else {
                    return 0;
                }
            }
        });

        for (String key : keys) {
            CustomData c = map.get(key);
            if (c != null) {
                Log.e("key:"+key+", CustomData:"+c.toString());
            } 
        }
    }

在compare方法中,val1.compareTo(val2)的代码有错别字,应该是else if ((val1 != null) && (val2 != null)) { return val1.compareTo(val2); }。 - Tanuj Verma

0
public static TreeMap<String, String> sortMap(HashMap<String, String> passedMap, String byParam) {
    if(byParam.trim().toLowerCase().equalsIgnoreCase("byValue")) {
        // Altering the (key, value) -> (value, key)
        HashMap<String, String> newMap =  new HashMap<String, String>();
        for (Map.Entry<String, String> entry : passedMap.entrySet()) {
            newMap.put(entry.getValue(), entry.getKey());
        }
        return new TreeMap<String, String>(newMap);
    }
    return new TreeMap<String, String>(passedMap);
}

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