基于版本:Guava 22.0

Wiki:Ordering

0. Ordering简介

Guava的Ordering提供了链式风格的比较器的实现,我们可以用Ordering轻松构建复杂的比较器。

1. 类图

这张类图不完全,实际上Ordering有十几个子类,这些子类共同提供了复杂的功能。

2. 设计思路

Ordering是继承于java.util.Comparator接口的抽象类,它的十几个子类都实现了compare与equals方法,这些子类可以实现基本的排序功能。

通过链式调用,可以将这些子类组合在一起,实现复杂的排序规则。

举个例子:

Ordering combineOrdering = Ordering.natural().reverse().nullsFirst();

对应的源码如下

Ordering.nullsFirst()
@GwtCompatible(serializable = true)
public <S extends T> Ordering<S> nullsFirst() {
return new NullsFirstOrdering<S>(this);
} Ordering.reverse()
@GwtCompatible(serializable = true)
public <S extends T> Ordering<S> reverse() {
return new ReverseOrdering<S>(this);
} NullsFirstOrdering.class
final Ordering<? super T> ordering; NullsFirstOrdering(Ordering<? super T> ordering) {
this.ordering = ordering;
} @Override
public int compare(@Nullable T left, @Nullable T right) {
if (left == right) {
return 0;
}
if (left == null) {
return RIGHT_IS_GREATER;
}
if (right == null) {
return LEFT_IS_GREATER;
}
return ordering.compare(left, right);
} ReverseOrdering.class
final Ordering<? super T> forwardOrder; ReverseOrdering(Ordering<? super T> forwardOrder) {
this.forwardOrder = checkNotNull(forwardOrder);
} @Override
public int compare(T a, T b) {
return forwardOrder.compare(b, a);
}

可以看出combineOrdering实际上是由外部的NullsFirstOrdering加上内部ReverseOrdering嵌套而成的

在调用combineOrdering.compare方法的时候,会先调用NullsFirstOrdering.compare方法,再调用ReverseOrdering.compare方法

这会导致排序完毕的集合,null元素在最前面,后面则是逆向元素

比方[2,1,3,null,0]的排序结果就是[null,3,2,1,0]

这就是链式调用的特点,从右向左,逐渐应用排序规则

3. CompoundOrdering

设想一个场景,我们需要对学生的月考成绩从高到低进行排序,如果两人分数相同,则按照学号排序。

很显然这两个规则对应于两个简单的比较器,但是如何将这两个排序规则结合起来呢?

此时我们需要CompoundOrdering

Ordering.compound方法会创建CompoundOrdering的实例,相关源码如下

Ordering.compound()
public <U extends T> Ordering<U> compound(Comparator<? super U> secondaryComparator) {
return new CompoundOrdering<U>(this, checkNotNull(secondaryComparator));
} public static <T> Ordering<T> compound(Iterable<? extends Comparator<? super T>> comparators) {
return new CompoundOrdering<T>(comparators);
} CompoundOrdering.class
final ImmutableList<Comparator<? super T>> comparators; CompoundOrdering(Comparator<? super T> primary, Comparator<? super T> secondary) {
this.comparators = ImmutableList.<Comparator<? super T>>of(primary, secondary);
} CompoundOrdering(Iterable<? extends Comparator<? super T>> comparators) {
this.comparators = ImmutableList.copyOf(comparators);
} @Override
public int compare(T left, T right) {
// Avoid using the Iterator to avoid generating garbage (issue 979).
int size = comparators.size();
for (int i = 0; i < size; i++) {
int result = comparators.get(i).compare(left, right);
if (result != 0) {
return result;
}
}
return 0;
}

很容易可以看出,CompoundOrdering内部维护了一个比较器列表,调用CompoundOrdering.compare方法时,会从头遍历这个列表,直到找到一个compare方法不返回0的比较器为止。

这样就实现了如果前面的比较器无法区分大小则调用后续的比较器进一步区分的语义。

与第二节讲到的链式调用的从右向左阅读规则不同的是,CompoundOrdering的阅读规则是从左向右。

Wiki上还特意提到(Exception to the "backwards" rule: For chains of calls to compound, read from left to right. To avoid confusion, avoid intermixing compound calls with other chained calls.) 为了避免理解上的混乱,请不要把compound写在一长串链式调用的中间,你可以另起一行,在链中最先或最后调用compound。

4. ByFunctionOrdering

Ordering的onResultOf方法提供了对先对集合内元素调用函数,然后再调用自定义比较器的解决方案

Ordering.onResultOf
public <F> Ordering<F> onResultOf(Function<F, ? extends T> function) {
return new ByFunctionOrdering<F, T>(function, this);
} ByFunctionOrdering.class
final class ByFunctionOrdering<F, T> extends Ordering<F> implements Serializable {
final Function<F, ? extends T> function;
final Ordering<T> ordering; ByFunctionOrdering(Function<F, ? extends T> function, Ordering<T> ordering) {
this.function = checkNotNull(function);
this.ordering = checkNotNull(ordering);
} @Override
public int compare(F left, F right) {
return ordering.compare(function.apply(left), function.apply(right));
}

代码非常简单,构造器中传入函数function与自定义比较器,然后compare方法中调用自定义比较器,对被function处理过的对象进行比较排序

举个例子,我们现在有一个实体类Student,里面有两个属性:String型的name,与Integer型的grade,现在我们需要对一个Student列表按照grade从高到低排序。

需要的比较器如下

    public static void main(String[] args) {
Ordering combineOrdering =
Ordering.natural().reverse().onResultOf(new Function<Student, Comparable>() {
@Nullable
@Override
public Comparable apply(@Nullable Student input) {
return
input.getGrade();
}
});

List<Student> list = Lists.newArrayList();
list.add(new Student("a", 60));
list.add(new Student("b", 90));
list.add(new Student("c", 80));
list.add(new Student("d", 30));
Collections.sort(list, combineOrdering);
System.out.println(list);
}

输出结果如下:

[Student{name='b', grade=90}, Student{name='c', grade=80}, Student{name='a', grade=60}, Student{name='d', grade=30}]

可以看到,结果确实被很好的排序了

5. Ordering提供的一些实用方法

Ordering.max()//遍历输入集合,取最大值
@CanIgnoreReturnValue // TODO(kak): Consider removing this
public <E extends T> E max(Iterator<E> iterator) {
// let this throw NoSuchElementException as necessary
E maxSoFar = iterator.next(); while (iterator.hasNext()) {
maxSoFar = max(maxSoFar, iterator.next());
} return maxSoFar;
} Ordering.min()//遍历输入集合,取最小值
@CanIgnoreReturnValue // TODO(kak): Consider removing this
public <E extends T> E min(Iterator<E> iterator) {
// let this throw NoSuchElementException as necessary
E minSoFar = iterator.next(); while (iterator.hasNext()) {
minSoFar = min(minSoFar, iterator.next());
} return minSoFar;
} Ordering.greatestOf()//对比较器做逆序操作,然后取逆序后的比较器的最小的k个值
public <E extends T> List<E> greatestOf(Iterator<E> iterator, int k) {
return reverse().leastOf(iterator, k);
} Ordering.leastOf
public <E extends T> List<E> leastOf(Iterator<E> iterator, int k) {
checkNotNull(iterator);
checkNonnegative(k, "k"); if (k == 0 || !iterator.hasNext()) {
return ImmutableList.of();
} else if (k >= Integer.MAX_VALUE / 2) {//如果k很大,则直接完全排序然后取头k个结果并返回
// k is really large; just do a straightforward sorted-copy-and-sublist
ArrayList<E> list = Lists.newArrayList(iterator);
Collections.sort(list, this);
if (list.size() > k) {
list.subList(k, list.size()).clear();
}
list.trimToSize();//压缩数组以释放内存
return Collections.unmodifiableList(list);
} else {//用quickselect算法取top k
TopKSelector<E> selector = TopKSelector.least(k, this);
selector.offerAll(iterator);
return selector.topK();
}
} Ordering.isOrdered()//遍历输入集合,检查集合是否有序
public boolean isOrdered(Iterable<? extends T> iterable) {
Iterator<? extends T> it = iterable.iterator();
if (it.hasNext()) {
T prev = it.next();
while (it.hasNext()) {
T next = it.next();
if (compare(prev, next) > 0) {
return false;
}
prev = next;
}
}
return true;
}

代码很简单

只有Ordering.leastOf方法有点意思,它会根据传入的k值分情况处理。

如果k < Integer.MAX_VALUE / 2,则调用经典的quickselect算法取TOP K。

如果k >= Integer.MAX_VALUE / 2,则先拷贝传入的集合,然后做全排序,然后取头k个结果,然后再压缩剩余的元素以节省内存。

为什么要这么设计,我不太明白。

我个人的想法是,既然Java的设定是一般集合的大小不能超过Integer.MAX_VALUE,那么如果k >= Integer.MAX_VALUE / 2,直接将比较器反向,然后取TOP(size - k)不就好了吗?

Guava源码学习(二)Ordering的更多相关文章

  1. Dubbo源码学习(二)

    @Adaptive注解 在上一篇ExtensionLoader的博客中记录了,有两种扩展点,一种是普通的扩展实现,另一种就是自适应的扩展点,即@Adaptive注解的实现类. @Documented ...

  2. python 协程库gevent学习--gevent源码学习(二)

    在进行gevent源码学习一分析之后,我还对两个比较核心的问题抱有疑问: 1. gevent.Greenlet.join()以及他的list版本joinall()的原理和使用. 2. 关于在使用mon ...

  3. Vue源码学习二 ———— Vue原型对象包装

    Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...

  4. 以太坊 layer2: optimism 源码学习(二) 提现原理

    作者:林冠宏 / 指尖下的幽灵.转载者,请: 务必标明出处. 掘金:https://juejin.im/user/1785262612681997 博客:http://www.cnblogs.com/ ...

  5. [spring源码学习]二、IOC源码——配置文件读取

    一.环境准备 对于学习源码来讲,拿到一大堆的代码,脑袋里肯定是嗡嗡的,所以从代码实例进行跟踪调试未尝不是一种好的办法,此处,我们准备了一个小例子: package com.zjl; public cl ...

  6. Guava源码学习(五)EventBus

    基于版本:Guava 22.0 Wiki:EventBus 0. EventBus简介 提供了发布-订阅模型,可以方便的在EventBus上注册订阅者,发布者可以简单的将事件传递给EventBus,E ...

  7. SocketServer源码学习(二)

    SocketServer 中非常重要的两个基类就是:BaseServer 和 BaseRequestHandler在SocketServer 中也提供了对TCP以及UDP的高级封装,这次我们主要通过分 ...

  8. Guava源码学习(零)前言

    Guava是由Google出品的Java类库,功能强大且易用. 后续我会用多篇博客介绍Guava的使用方法,以及从源码层面分析其实现原理. 分析次序基于Guava的官方Wiki 基于版本:Guava ...

  9. Thrift源码学习二——Server层

    Thrift 提供了如图五种模式:TSimpleServer.TNonblockingServer.THsHaServer.TThreadPoolServer.TThreadSelectorServe ...

随机推荐

  1. laravel5.5中间件

    目录 1. 中间件知识 1. artisan 命令 2. 文件内容 3. 前置中间件和后置中间件 4. 使用中间件 2. 控制器中间件 1. 中间件知识 1. artisan 命令 php artis ...

  2. Java入门 手把手教你配置环境变量

    很多人觉得配置Java开发的环境变量很麻烦,很容易忘记,时常被它搞得晕头转向.如果出现这样的情况,那么原因只有一个,你不了解为毛需要配置环境变量,不配置环境变量就不能开发了吗? 答案是:NO!,那么下 ...

  3. 《Cracking the Coding Interview》——第2章:链表——题目4

    2014-03-18 02:27 题目:将一个单链表按照一个值X分为两部分,小于X的部分放在大于等于X的部分之前. 解法:按照值和X的大小,分链表为两条链表,然后连起来成一条. 代码: // 2.4 ...

  4. 关于Ckpalyer播放器的MP4无法播放问题

    此文是从网上摘要的 有时在本地使用ckplayer来播放视频,flv格式非常容易的就播放了,但是使用mp4格式却显示:加载失败.为什么呢?        首页看下你i的本地站点MIME类型中,是否增加 ...

  5. sources-t.list

    deb http://debian.ustc.edu.cn/ubuntu/ trusty main multiverse restricted universe deb http://debian.u ...

  6. hnust 分蛋糕

    问题 B: 分蛋糕 时间限制: 1 Sec  内存限制: 128 MB提交: 2430  解决: 966[提交][状态][讨论版] 题目描述 今天是DK生日,由于DK的朋友很多,所以DK在蛋糕店定制了 ...

  7. springbootday06 mysql

    一.MySql 1. 数据库概述 数据库( Database )是按照数据结构来组织.存储和管理数据的仓库 . 数据按照特定的格式存储起来,用户可以通过SQL (Structured Query La ...

  8. c# json 反序列化 泛型List 2行代码

    List<EncyTable> list = new List<EncyTable>(); var jsonReqeust = "[{ENCY_ID:775,ENCY ...

  9. Android M中 JNI的入门学习

    今年谷歌推出了Android 6.0,作为安卓开发人员,对其学习掌握肯定是必不可少的,今天小编和大家分享的就是Android 6.0中的 JNI相关知识,这是在一个安卓教程网上看到的内容,感觉很不错, ...

  10. P1270 “访问”美术馆

    题目描述 经过数月的精心准备,Peer Brelstet,一个出了名的盗画者,准备开始他的下一个行动.艺术馆的结构,每条走廊要么分叉为两条走廊,要么通向一个展览室.Peer知道每个展室里藏画的数量,并 ...