1、前言
针对集合排序,我们通常都会借助具有排序功能的集合,来处理我们的数据。比如ArrayList,TreeMap等。但是使用不同的排序工具,可能会遇到不同的问题。
2、案例分析
2.1 需求:根据HashMap中的数据,按照value排序。例如:
/*
排序之前的结果:
key:value = a:3
key:value = b:5
key:value = c:1
key:value = d:4
key:value = e:2
要求输出结果为:
key:value = c:1
key:value = e:2
key:value = a:3
key:value = d:4
key:value = b:5
*/
Map<String, Integer> map = new HashMap<>();
map.put("d", 4);
map.put("e", 2);
map.put("a", 3);
map.put("b", 5);
map.put("c", 1);
2.2 根据Map的特性,里面存储的是一个个kv结构的 Entry,可以借助ArrayList的有序性,将 Entry 放到List中排序后取出。如下:
// 借助ArrayList排序
List<Map.Entry<String, Integer>> entryList = Lists.newArrayList(map.entrySet());
entryList.sort((o1, o2)->{return o1.getValue()-o2.getValue();});
entryList.forEach(c -> System.out.println("key:value = " + c.getKey() + ":" + c.getValue()));
/*
排序结果:
key:value = c:1
key:value = e:2
key:value = a:3
key:value = d:4
key:value = b:5
*/
2.3 Map中本身也具有排序的集合,如 TreeMap 。但是 TreeMap 传入比较器的参数默认都是Key作为入参,为了取出Value进行比较,需要将对象作为参数传进去,以获取Value值。需要我们定制比较器。如下:
public class MapValueComparator implements Comparator<String> {
Map<String, Integer> map;
public MapValueComparator(Map<String, Integer> map){
this.map = map;
}
@Override
public int compare(String o1, String o2) {
return map.get(o1)-map.get(o2);
}
}
通过 TreeMap
// 通过TreeMap排序
TreeMap<String, Integer> treeMap = new TreeMap<>(new MapValueComparator(map));
treeMap.putAll(map);
treeMap.forEach((k,v) -> System.out.println("key:value = " + k + ":" + v));
/*
排序结果:
key:value = c:1
key:value = e:2
key:value = a:3
key:value = d:4
key:value = b:5
*/
2.4 以上两种排序方式均可以完成指定功能排序。但是如果Value重复,是否会还是保持原来的结果呢?如图:
public class TreeMapTest {
@Test
public void test01() {
Map<String, Integer> map = new HashMap<>();
map.put("d", 4);
map.put("e", 2);
map.put("a", 3);
map.put("b", 5);
map.put("c", 1);
map.put("f", 2);
System.out.println("------------自然顺序-------------------");
map.forEach((k,v) -> System.out.println("key:value = " + k + ":" + v));
// 方法1:借助List排序
System.out.println("------------借助List排序-------------------");
List<Map.Entry<String, Integer>> entryList = Lists.newArrayList(map.entrySet());
entryList.sort((o1, o2)->{return o1.getValue()-o2.getValue();});
entryList.forEach(c -> System.out.println("key:value = " + c.getKey() + ":" + c.getValue()));
// 方法2:借助treeMap按照value排序
System.out.println("------------treeMap排序-------------------");
TreeMap<String, Integer> treeMap = new TreeMap<>(new MapValueComparator(map));
treeMap.putAll(map);
treeMap.forEach((k,v) -> System.out.println("key:value = " + k + ":" + v));
/*
* 执行结果:
* ------------自然顺序-------------------
key:value = a:3
key:value = b:5
key:value = c:1
key:value = d:4
key:value = e:2
key:value = f:2
------------借助List排序-------------------
key:value = c:1
key:value = e:2
key:value = f:2
key:value = a:3
key:value = d:4
key:value = b:5
------------treeMap排序-------------------
key:value = c:1
key:value = e:2
key:value = a:3
key:value = d:4
key:value = b:5
*
*
* */
}
}
从运行的结果来看。List的排序依然没有问题,但是TreeMap的结果 丢失数据 。我们来分析一下TreeMap为什么会丢失数据。
3、TreeMap 源码的分析追踪
3.1 追踪 putAll() 方法:
3.2 追踪 put() 方法:
从源码中可以看到,put第一个值时,直接放入TreeMap中。第二个开始就会调用比较器compare方法。比较结果小于0就将当前的数据放在节点的左侧;如果大于0,就放在节点的右侧;等于0时,也就是比较的结果就会覆盖当前值。
这也就是比较器结果相同的时,为什么数据会被覆盖掉的原因。这里是Map集合的key不会重复,相同是就会覆盖的特性。
4、如何处理这种数据
只需要加在定义比较器的时候,不返回等于0的值,对于等于0的数据特殊处理即可。如下:
public class MapValueComparator implements Comparator<String> {
private Map<String, Integer> map;
public MapValueComparator(Map<String,Integer> map){
this.map = map;
}
@Override
public int compare(String o1, String o2) {
int o1v = map.get(o1);
int o2v = map.get(o2);
// 特殊处理
return o1v-o2v == 0 ? 1 : o1v-o2v;
}
}