学而实习之 不亦乐乎

对 Java 中的 map 集合排序

2022-08-26 21:43:22

一、Map简介

Map 是键值对的集合接口,它的实现类主要包括:HashMap,TreeMap,Hashtable以及LinkedHashMap等。其区别如下:

HashMap:最常用的Map,它根据key的HashCode 值来存储数据,根据 key 可以直接获取它的 Value,同时它具有很快的访问速度。HashMap 最多只允许一条记录的 key 值为 Null(多条会覆盖);允许多条记录的 Value 为 Null。
TreeMap: 能够把它保存的记录根据 key 排序,默认是按升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。TreeMap 不允许 key 的值为 null。

Hashtable: 与 HashMap 类似,不同的是 key 和 value 的值均不允许为 null;它支持线程的同步,即任一时刻只有一个线程能写 Hashtable,因此也导致了 Hashtale 在写入时会比较慢。

LinkedHashMap: 保存了记录的插入顺序,在用 Iterator 遍历 LinkedHashMap 时,先得到的记录肯定是先插入的,在遍历的时候会比 HashMap 慢。key 和value 均允许为空。

二、Map排序

1、TreeMap排序

TreeMap默认是升序的,如果我们需要改变排序方式,则需要使用比较器:Comparator。

Comparator可以对集合对象或者数组进行排序的比较器接口,实现该接口的public compare(T o1,To2) 方法即可实现排序,该方法主要是根据第一个参数 o1,小于、等于或者大于 o2 分别返回负整数、0或者正整数。如下:

public class TreeMapTest {
    public static void main(String[] args) {
        Map<String, String> map = new TreeMap<String, String>(
                new Comparator<String>() {
                    public int compare(String obj1, String obj2) {
                        // 降序排序
                        return obj2.compareTo(obj1);
                    }
                });
        map.put("c", "ccccc");
        map.put("a", "aaaaa");
        map.put("b", "bbbbb");
        map.put("d", "ddddd");
        
        Set<String> keySet = map.keySet();
        Iterator<String> iter = keySet.iterator();
        while (iter.hasNext()) {
            String key = iter.next();
            System.out.println(key + ":" + map.get(key));
        }
    }
}

运行结果如下:

      d:ddddd 
      c:ccccc 
      b:bbbbb 
      a:aaaaa

上面例子是对根据TreeMap的key值来进行排序的,但是有时我们需要根据 TreeMap 的 value 来进行排序。对 value 排序我们就需要借助于 Collections 的 sort(List<T> list, Comparator<? super T> c)方法,该方法根据指定比较器产生的顺序对指定列表进行排序。但是有一个前提条件,那就是所有的元素都必须能够根据所提供的比较器来进行比较。如下:

public class TreeMapTest {
    public static void main(String[] args) {
        Map<String, String> map = new TreeMap<String, String>();
        map.put("d", "ddddd");
        map.put("b", "bbbbb");
        map.put("a", "aaaaa");
        map.put("c", "ccccc");
        
        //这里将map.entrySet()转换成list
        List<Map.Entry<String,String>> list = new ArrayList<Map.Entry<String,String>>(map.entrySet());
        //然后通过比较器来实现排序
        Collections.sort(list,new Comparator<Map.Entry<String,String>>() {
            //升序排序
            public int compare(Entry<String, String> o1,
                    Entry<String, String> o2) {
                return o1.getValue().compareTo(o2.getValue());
            }            
        });
        
        for(Map.Entry<String,String> mapping:list){ 
               System.out.println(mapping.getKey()+":"+mapping.getValue()); 
          } 
    }
}

运行结果

      a:aaaaa 
      b:bbbbb 
      c:ccccc 
      d:ddddd

2、HashMap排序

【1】按key排序

public class SortByKeyExample {

    public static void main(String[] args) {
        Map<String, Integer> unsortMap = new HashMap<>();
        unsortMap.put("z", 10);
        unsortMap.put("b", 5);
        unsortMap.put("a", 6);
        unsortMap.put("c", 20);
        unsortMap.put("d", 1);
        unsortMap.put("e", 7);
        unsortMap.put("y", 8);
        unsortMap.put("n", 99);
        unsortMap.put("g", 50);
        unsortMap.put("m", 2);
        unsortMap.put("f", 9);
        System.out.println(unsortMap);

        Map<String, Integer> result1 = unsortMap.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                        (oleValue, newValue) -> oleValue, LinkedHashMap::new));
        System.out.println(result1);

        Map<String, Integer> result2 = new LinkedHashMap<>();
        unsortMap.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .forEachOrdered(x -> result2.put(x.getKey(), x.getValue()));
        System.out.println(result2);
    }
}

【2】按Vaule排序

public class SoryByKeyExample {

    public static void main(String[] args) {
        Map<String, Integer> unsortMap = new HashMap<>();
        unsortMap.put("z", 10);
        unsortMap.put("b", 5);
        unsortMap.put("a", 6);
        unsortMap.put("c", 20);
        unsortMap.put("d", 1);
        unsortMap.put("e", 7);
        unsortMap.put("y", 8);
        unsortMap.put("n", 99);
        unsortMap.put("g", 50);
        unsortMap.put("m", 2);
        unsortMap.put("f", 9);
        System.out.println(unsortMap);

        Map<String, Integer> result1 = unsortMap.entrySet().stream()
                .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                        (oldValue, newValue) -> oldValue, LinkedHashMap::new));

        System.out.println(result1);

        Map<String, Integer> result2 = new LinkedHashMap<>();
        unsortMap.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
                .forEachOrdered(x -> result2.put(x.getKey(), x.getValue()));
        System.out.println(result2);
    }
}

3、按键排序(sort by key)

jdk内置的java.util包下的TreeMap<K,V>既可满足此类需求,原理很简单,其重载的构造器之一。

有一个参数,该参数接受一个比较器,比较器定义比较规则,比较规则就是作用于 TreeMap<K,V> 的键,据此可实现按键排序。

public Map<String, String> sortMapByKey(Map<String, String> oriMap) {
    if (oriMap == null || oriMap.isEmpty()) {
        return null;
    }
    Map<String, String> sortedMap = new TreeMap<String, String>(new Comparator<String>() {
        public int compare(String key1, String key2) {
            int intKey1 = 0, intKey2 = 0;
            try {
                intKey1 = getInt(key1);
                intKey2 = getInt(key2);
            } catch (Exception e) {
                intKey1 = 0; 
                intKey2 = 0;
            }
            return intKey1 - intKey2;
        }});
    sortedMap.putAll(oriMap);
    return sortedMap;
}

private int getInt(String str) {
    int i = 0;
    try {
        Pattern p = Pattern.compile("^\\d+");
        Matcher m = p.matcher(str);
        if (m.find()) {
            i = Integer.valueOf(m.group());
        }
    } catch (NumberFormatException e) {
        e.printStackTrace();
    }
    return i;
}

4、按值排序(sort by value)

按值排序麻相对复杂一些,需要自行转换一下。

Map本身按值排序是很有意义的,很多场合下都会遇到类似需求,可以认为其值是定义的某种规则或者权重。

public Map<String, String> sortMapByValue(Map<String, String> oriMap) {
    Map<String, String> sortedMap = new LinkedHashMap<String, String>();
    if (oriMap != null && !oriMap.isEmpty()) {
        List<Map.Entry<String, String>> entryList = new ArrayList<Map.Entry<String, String>>(oriMap.entrySet());
        Collections.sort(entryList,
                new Comparator<Map.Entry<String, String>>() {
                    public int compare(Entry<String, String> entry1,
                            Entry<String, String> entry2) {
                        int value1 = 0, value2 = 0;
                        try {
                            value1 = getInt(entry1.getValue());
                            value2 = getInt(entry2.getValue());
                        } catch (NumberFormatException e) {
                            value1 = 0;
                            value2 = 0;
                        }
                        return value2 - value1;
                    }
                });
        Iterator<Map.Entry<String, String>> iter = entryList.iterator();
        Map.Entry<String, String> tmpEntry = null;
        while (iter.hasNext()) {
            tmpEntry = iter.next();
            sortedMap.put(tmpEntry.getKey(), tmpEntry.getValue());
        }
    }
    return sortedMap;
}

本例中先将待排序 oriMap 中的所有元素置于一个列表中,接着使用 java.util.Collections 的一个静态方法来排序列表,同样是用比较器定义比较规则。排序后的列表中的元素再依次被装入Map,需要注意的一点是为了肯定的保证 Map 中元素与排序后的 List 中的元素的顺序一致,使用了 LinkedHashMap 数据类型,虽然该类型不常见,但是在一些特殊场合下还是非常有用的。